/*
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using System.Text;
using Amazon.Runtime;
using Amazon.S3.Util;
using Amazon.Util;
using System.Globalization;
using Amazon.S3.Model.Internal.MarshallTransformations;
using Amazon.S3;
namespace Amazon.S3.Model
{
///
/// Returns information about the GetObject response and response metadata.
///
public partial class GetObjectResponse : StreamResponse
{
private string deleteMarker;
private string acceptRanges;
private string contentRange;
private Expiration expiration;
private DateTime? restoreExpiration;
private bool restoreInProgress;
private DateTime? lastModified;
private string eTag;
private int? missingMeta;
private string versionId;
private DateTime? expires;
private ObjectLockLegalHoldStatus objectLockLegalHoldStatus;
private ObjectLockMode objectLockMode;
private DateTime? objectLockRetainUntilDate;
private string websiteRedirectLocation;
private ServerSideEncryptionMethod serverSideEncryption;
private ServerSideEncryptionCustomerMethod serverSideEncryptionCustomerMethod;
private string serverSideEncryptionKeyManagementServiceKeyId;
private HeadersCollection headersCollection = new HeadersCollection();
private MetadataCollection metadataCollection = new MetadataCollection();
private ReplicationStatus replicationStatus;
private int? partsCount;
private S3StorageClass storageClass;
private RequestCharged requestCharged;
private int? tagCount;
private string bucketName;
private string key;
///
/// Flag which returns true if the Expires property has been unmarshalled
/// from the raw value or set by user code.
///
private bool isExpiresUnmarshalled;
internal string RawExpires { get; set; }
///
/// Gets and sets the BucketName property.
///
public string BucketName
{
get { return this.bucketName; }
set { this.bucketName = value; }
}
///
/// Gets and sets the Key property.
///
public string Key
{
get { return this.key; }
set { this.key = value; }
}
///
/// Specifies whether the object retrieved was (true) or was not (false) a Delete Marker. If false, this response header does not appear in the
/// response.
///
///
public string DeleteMarker
{
get { return this.deleteMarker; }
set { this.deleteMarker = value; }
}
// Check to see if DeleteMarker property is set
internal bool IsSetDeleteMarker()
{
return this.deleteMarker != null;
}
///
/// The collection of headers for the request.
///
public HeadersCollection Headers
{
get
{
if (this.headersCollection == null)
this.headersCollection = new HeadersCollection();
return this.headersCollection;
}
}
///
/// The collection of meta data for the request.
///
public MetadataCollection Metadata
{
get
{
if (this.metadataCollection == null)
this.metadataCollection = new MetadataCollection();
return this.metadataCollection;
}
}
///
/// Gets and sets the AcceptRanges.
///
public string AcceptRanges
{
get { return this.acceptRanges; }
set { this.acceptRanges = value; }
}
// Check to see if AcceptRanges property is set
internal bool IsSetAcceptRanges()
{
return this.acceptRanges != null;
}
///
/// Gets and sets the ContentRange.
///
public string ContentRange
{
get { return this.contentRange; }
set { this.contentRange = value; }
}
// Check to see if ContentRange property is set
internal bool IsSetContentRange()
{
return this.contentRange != null;
}
///
/// Gets and sets the Expiration property.
/// Specifies the expiration date for the object and the
/// rule governing the expiration.
/// Is null if expiration is not applicable.
///
public Expiration Expiration
{
get { return this.expiration; }
set { this.expiration = value; }
}
// Check to see if Expiration property is set
internal bool IsSetExpiration()
{
return this.expiration != null;
}
///
/// Gets and sets the RestoreExpiration property.
/// RestoreExpiration will be set for objects that have been restored from Amazon Glacier.
/// It indiciates for those objects how long the restored object will exist.
///
public DateTime? RestoreExpiration
{
get { return this.restoreExpiration; }
set { this.restoreExpiration = value; }
}
///
/// Gets and sets the RestoreInProgress
/// Will be true when the object is in the process of being restored from Amazon Glacier.
///
public bool RestoreInProgress
{
get { return this.restoreInProgress; }
set { this.restoreInProgress = value; }
}
///
/// Last modified date of the object
///
///
public DateTime LastModified
{
get { return this.lastModified ?? default(DateTime); }
set { this.lastModified = value; }
}
// Check to see if LastModified property is set
internal bool IsSetLastModified()
{
return this.lastModified.HasValue;
}
///
/// An ETag is an opaque identifier assigned by a web server to a specific version of a resource found at a URL
///
///
public string ETag
{
get { return this.eTag; }
set { this.eTag = value; }
}
// Check to see if ETag property is set
internal bool IsSetETag()
{
return this.eTag != null;
}
///
/// This is set to the number of metadata entries not returned in x-amz-meta headers. This can happen if you create metadata using an API like
/// SOAP that supports more flexible metadata than the REST API. For example, using SOAP, you can create metadata whose values are not legal
/// HTTP headers.
///
///
public int MissingMeta
{
get { return this.missingMeta ?? default(int); }
set { this.missingMeta = value; }
}
// Check to see if MissingMeta property is set
internal bool IsSetMissingMeta()
{
return this.missingMeta.HasValue;
}
///
/// Version of the object.
///
///
public string VersionId
{
get { return this.versionId; }
set { this.versionId = value; }
}
// Check to see if VersionId property is set
internal bool IsSetVersionId()
{
return this.versionId != null;
}
///
/// The date and time at which the object is no longer cacheable.
///
///
public DateTime Expires
{
get
{
if (this.isExpiresUnmarshalled)
{
return this.expires.Value;
}
else
{
this.expires = AmazonS3Util.ParseExpiresHeader(this.RawExpires, this.ResponseMetadata.RequestId);
this.isExpiresUnmarshalled = true;
return this.expires.Value;
}
}
set { this.expires = value; this.isExpiresUnmarshalled = true; }
}
// Check to see if Expires property is set
internal bool IsSetExpires()
{
return this.expires.HasValue;
}
///
/// Gets and sets the property ObjectLockLegalHoldStatus.
///
public ObjectLockLegalHoldStatus ObjectLockLegalHoldStatus
{
get { return this.objectLockLegalHoldStatus; }
set { this.objectLockLegalHoldStatus = value; }
}
// Check to see if ObjectLockLegalHoldStatus property is set
internal bool IsSetObjectLockLegalHoldStatus()
{
return this.objectLockLegalHoldStatus != null;
}
///
/// Gets and sets the property ObjectLockMode.
///
/// The Object Lock mode currently in place for this object.
///
///
public ObjectLockMode ObjectLockMode
{
get { return this.objectLockMode; }
set { this.objectLockMode = value; }
}
// Check to see if ObjectLockMode property is set
internal bool IsSetObjectLockMode()
{
return this.objectLockMode != null;
}
///
/// Gets and sets the property ObjectLockRetainUntilDate.
///
/// The date and time when this object's Object Lock will expire.
///
///
public DateTime ObjectLockRetainUntilDate
{
get { return this.objectLockRetainUntilDate.GetValueOrDefault(); }
set { this.objectLockRetainUntilDate = value; }
}
// Check to see if ObjectLockRetainUntilDate property is set
internal bool IsSetObjectLockRetainUntilDate()
{
return this.objectLockRetainUntilDate.HasValue;
}
///
/// If the bucket is configured as a website, redirects requests for this object to another object in the same bucket or to an external URL.
/// Amazon S3 stores the value of this header in the object metadata.
///
///
public string WebsiteRedirectLocation
{
get { return this.websiteRedirectLocation; }
set { this.websiteRedirectLocation = value; }
}
// Check to see if WebsiteRedirectLocation property is set
internal bool IsSetWebsiteRedirectLocation()
{
return this.websiteRedirectLocation != null;
}
///
/// The Server-side encryption algorithm used when storing this object in S3.
///
///
public ServerSideEncryptionMethod ServerSideEncryptionMethod
{
get { return this.serverSideEncryption; }
set { this.serverSideEncryption = value; }
}
// Check to see if ServerSideEncryptionMethod property is set
internal bool IsSetServerSideEncryptionMethod()
{
return this.serverSideEncryption != null;
}
///
/// The class of storage used to store the object.
///
///
public S3StorageClass StorageClass
{
get { return this.storageClass; }
set { this.storageClass = value; }
}
// Check to see if StorageClass property is set
internal bool IsSetStorageClass()
{
return this.storageClass != null;
}
///
/// The id of the AWS Key Management Service key that Amazon S3 uses to encrypt and decrypt the object.
///
public string ServerSideEncryptionKeyManagementServiceKeyId
{
get { return this.serverSideEncryptionKeyManagementServiceKeyId; }
set { this.serverSideEncryptionKeyManagementServiceKeyId = value; }
}
///
/// Checks if ServerSideEncryptionKeyManagementServiceKeyId property is set.
///
/// true if ServerSideEncryptionKeyManagementServiceKeyId property is set.
internal bool IsSetServerSideEncryptionKeyManagementServiceKeyId()
{
return !System.String.IsNullOrEmpty(this.serverSideEncryptionKeyManagementServiceKeyId);
}
///
/// The status of the replication job associated with this source object.
///
public ReplicationStatus ReplicationStatus
{
get { return this.replicationStatus; }
set { this.replicationStatus = value; }
}
///
/// Checks if ReplicationStatus property is set.
///
/// true if ReplicationStatus property is set.
internal bool IsSetReplicationStatus()
{
return ReplicationStatus != null;
}
///
/// The number of parts this oject has.
///
public int? PartsCount
{
get { return this.partsCount; }
set { this.partsCount = value; }
}
///
/// Checks if PartsCount is set.
///
/// true if PartsCount property is set.
internal bool IsSetPartsCount()
{
return this.partsCount.HasValue;
}
///
/// The Server-side encryption algorithm to be used with the customer provided key.
///
///
public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod
{
get
{
if (this.serverSideEncryptionCustomerMethod == null)
return ServerSideEncryptionCustomerMethod.None;
return this.serverSideEncryptionCustomerMethod;
}
set { this.serverSideEncryptionCustomerMethod = value; }
}
///
/// If present, indicates that the requester was successfully charged for the request.
///
public RequestCharged RequestCharged
{
get { return this.requestCharged; }
set { this.requestCharged = value; }
}
///
/// Checks to see if RequestCharged is set.
///
/// true, if RequestCharged property is set.
internal bool IsSetRequestCharged()
{
return requestCharged != null;
}
///
/// The number of tags, if any, on the object.
///
public int TagCount
{
get { return this.tagCount ?? 0; }
set { this.tagCount = value; }
}
#if BCL
///
/// Writes the content of the ResponseStream a file indicated by the filePath argument.
///
/// The location where to write the ResponseStream
public void WriteResponseStreamToFile(string filePath)
{
WriteResponseStreamToFile(filePath, false);
}
///
/// Writes the content of the ResponseStream a file indicated by the filePath argument.
///
/// The location where to write the ResponseStream
/// Whether or not to append to the file if it exists
public void WriteResponseStreamToFile(string filePath, bool append)
{
CreateDirectory(filePath);
Stream downloadStream = CreateDownloadStream(filePath, append);
using (downloadStream)
{
long current = 0;
byte[] buffer = new byte[S3Constants.DefaultBufferSize];
int bytesRead = 0;
long totalIncrementTransferred = 0;
while ((bytesRead = this.ResponseStream.Read(buffer, 0, buffer.Length)) > 0)
{
downloadStream.Write(buffer, 0, bytesRead);
current += bytesRead;
totalIncrementTransferred += bytesRead;
if (totalIncrementTransferred >= AWSSDKUtils.DefaultProgressUpdateInterval)
{
this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:false);
totalIncrementTransferred = 0;
}
}
ValidateWrittenStreamSize(current);
// Encrypted objects may have size smaller than the total amount of data transfered due to padding.
// Instead of changing the file size or the total downloaded size, pass a flag that indicate that the transfer is complete.
this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:true);
}
}
private static Stream CreateDownloadStream(string filePath, bool append)
{
Stream downloadStream;
if (append && File.Exists(filePath))
downloadStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read, S3Constants.DefaultBufferSize);
else
downloadStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, S3Constants.DefaultBufferSize);
return downloadStream;
}
private static void CreateDirectory(string filePath)
{
// Make sure the directory exists to write too.
FileInfo fi = new FileInfo(filePath);
Directory.CreateDirectory(fi.DirectoryName);
}
#endif
#if !UNITY
#region Progress Event
///
/// The event for Write Object progress notifications. All
/// subscribers will be notified when a new progress
/// event is raised.
///
///
/// Subscribe to this event if you want to receive
/// put object progress notifications. Here is how:
/// 1. Define a method with a signature similar to this one:
///
/// private void displayProgress(object sender, WriteObjectProgressArgs args)
/// {
/// Console.WriteLine(args);
/// }
///
/// 2. Add this method to the Put Object Progress Event delegate's invocation list
///
/// GetObjectResponse response = s3Client.GetObject(request);
/// response.WriteObjectProgressEvent += displayProgress;
///
///
public event EventHandler WriteObjectProgressEvent;
#endregion
///
/// This method is called by a producer of write object progress
/// notifications. When called, all the subscribers in the
/// invocation list will be called sequentially.
///
/// The file being written.
/// The number of bytes transferred since last event
/// The number of bytes transferred
/// The total number of bytes to be transferred
/// True if transfer is complete
internal void OnRaiseProgressEvent(string file, long incrementTransferred, long transferred, long total, bool completed)
{
AWSSDKUtils.InvokeInBackground(WriteObjectProgressEvent, new WriteObjectProgressArgs(this.BucketName, this.Key, file, this.VersionId, incrementTransferred, transferred, total, completed), this);
}
private void ValidateWrittenStreamSize(long bytesWritten)
{
#if !PCL
// Check if response stream or it's base stream is a AESDecryptionStream
var stream = Runtime.Internal.Util.WrapperStream.SearchWrappedStream(this.ResponseStream,
(s => s is Runtime.Internal.Util.DecryptStream));
// Don't validate length if response is an encrypted object.
if (stream!=null)
return;
#endif
if (bytesWritten != this.ContentLength)
{
string amzId2;
if(!this.ResponseMetadata.Metadata.TryGetValue(HeaderKeys.XAmzId2Header, out amzId2))
amzId2 = string.Empty;
string amzCfId;
if(!this.ResponseMetadata.Metadata.TryGetValue(HeaderKeys.XAmzCloudFrontIdHeader, out amzCfId))
amzCfId = string.Empty;
string message = null;
if (string.IsNullOrEmpty(amzCfId))
{
message = string.Format(CultureInfo.InvariantCulture,
"The total bytes read {0} from response stream is not equal to the Content-Length {1} for the object {2} in bucket {3}." +
" Request ID = {4} , AmzId2 = {5}.",
bytesWritten, this.ContentLength, this.Key, this.BucketName, this.ResponseMetadata.RequestId, amzId2);
}
else
{
message = string.Format(CultureInfo.InvariantCulture,
"The total bytes read {0} from response stream is not equal to the Content-Length {1} for the object {2} in bucket {3}." +
" Request ID = {4} , AmzId2 = {5} , AmzCfId = {6}.",
bytesWritten, this.ContentLength, this.Key, this.BucketName, this.ResponseMetadata.RequestId, amzId2, amzCfId);
}
throw new StreamSizeMismatchException(message, this.ContentLength, bytesWritten, this.ResponseMetadata.RequestId, amzId2, amzCfId);
}
}
#if BCL45 || NETSTANDARD
///
/// Writes the content of the ResponseStream a file indicated by the filePath argument.
///
/// The location where to write the ResponseStream
/// Whether or not to append to the file if it exists
/// Cancellation token which can be used to cancel this operation.
public async System.Threading.Tasks.Task WriteResponseStreamToFileAsync(string filePath, bool append, System.Threading.CancellationToken cancellationToken)
{
// Make sure the directory exists to write too.
FileInfo fi = new FileInfo(filePath);
Directory.CreateDirectory(fi.DirectoryName);
Stream downloadStream;
if (append && File.Exists(filePath))
downloadStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read, S3Constants.DefaultBufferSize);
else
downloadStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, S3Constants.DefaultBufferSize);
try
{
long current = 0;
#if NETSTANDARD
Stream stream = this.ResponseStream;
#else
Stream stream = new BufferedStream(this.ResponseStream);
#endif
byte[] buffer = new byte[S3Constants.DefaultBufferSize];
int bytesRead = 0;
long totalIncrementTransferred = 0;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)
.ConfigureAwait(continueOnCapturedContext: false)) > 0)
{
cancellationToken.ThrowIfCancellationRequested();
await downloadStream.WriteAsync(buffer, 0, bytesRead)
.ConfigureAwait(continueOnCapturedContext: false);
current += bytesRead;
totalIncrementTransferred += bytesRead;
if (totalIncrementTransferred >= AWSSDKUtils.DefaultProgressUpdateInterval)
{
this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:false);
totalIncrementTransferred = 0;
}
}
ValidateWrittenStreamSize(current);
// Encrypted objects may have size smaller than the total amount of data trasnfered due to padding.
// Instead of changing the file size or the total downloaded size, pass a flag that indicate that the transfer is complete.
this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:true);
}
finally
{
downloadStream.Dispose();
}
}
#endif
#endif
}
#if !UNITY
///
/// Encapsulates the information needed to provide
/// download progress for the Write Object Event.
///
public class WriteObjectProgressArgs : TransferProgressArgs
{
///
/// The constructor takes the number of
/// currently transferred bytes and the
/// total number of bytes to be transferred
///
/// The bucket name for the S3 object being written.
/// The object key for the S3 object being written.
/// The version-id of the S3 object.
/// The number of bytes transferred since last event
/// The number of bytes transferred
/// The total number of bytes to be transferred
/// True if finished writing
internal WriteObjectProgressArgs(string bucketName, string key, string versionId, long incrementTransferred, long transferred, long total, bool completed)
: base(incrementTransferred, transferred, total)
{
this.BucketName = bucketName;
this.Key = key;
this.VersionId = versionId;
this.IsCompleted = completed;
}
///
/// The constructor takes the number of
/// currently transferred bytes and the
/// total number of bytes to be transferred
///
/// The bucket name for the S3 object being written.
/// The object key for the S3 object being written.
/// The file for the S3 object being written.
/// The version-id of the S3 object.
/// The number of bytes transferred since last event
/// The number of bytes transferred
/// The total number of bytes to be transferred
/// True if finished writing
internal WriteObjectProgressArgs(string bucketName, string key, string filePath, string versionId, long incrementTransferred, long transferred, long total, bool completed)
: base(incrementTransferred, transferred, total)
{
this.BucketName = bucketName;
this.Key = key;
this.VersionId = versionId;
this.FilePath = filePath;
this.IsCompleted = completed;
}
///
/// Gets the bucket name for the S3 object being written.
///
public string BucketName { get; private set; }
///
/// Gets the object key for the S3 object being written.
///
public string Key { get; private set; }
///
/// Gets the version-id of the S3 object.
///
public string VersionId { get; private set; }
///
/// The file for the S3 object being written.
///
public string FilePath { get; private set; }
///
/// True if writing is complete
///
public bool IsCompleted { get; private set; }
}
#endif
}