Skip to content

Allow resumable upload session initiation #890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Google.Cloud.Storage.V1.Snippets
Expand Down Expand Up @@ -272,6 +273,44 @@ public void UploadObject()
Assert.Equal(source, _fixture.WorldLocalFileName);
}


[Fact]
public async Task UploadObjectWithSessionUri()
{
var bucketName = _fixture.BucketName;

// Snippet: UploadObjectWithSessionUri
var client = StorageClient.Create();
var source = "world.txt";
var destination = "places/world.txt";
var contentType = "text/plain";

// var acl = PredefinedAcl.PublicRead // public
var acl = PredefinedObjectAcl.AuthenticatedRead; // private
var options = new UploadObjectOptions { PredefinedAcl = acl };
// Create a temporary uploader so the upload session can be manually initiated without actually uploading.
var tempUploader = client.CreateObjectUploader(bucketName, destination, contentType, new MemoryStream(), options);
var uploadUri = await tempUploader.InitiateSessionAsync();

// Send uploadUri to (unauthenticated) client application, so it can perform the upload:
using (var stream = File.OpenRead(source))
{
// IUploadProgress defined in Google.Apis.Upload namespace
IProgress<IUploadProgress> progress = new Progress<IUploadProgress>(
p => Console.WriteLine($"bytes: {p.BytesSent}, status: {p.Status}")
);

var actualUploader = ResumableUpload.CreateFromUploadUri(uploadUri, stream);
actualUploader.ProgressChanged += progress.Report;
await actualUploader.UploadAsync();
}
// End snippet

// want to show the source in the snippet, but also
// want to make sure it matches the one in the fixture
Assert.Equal(source, _fixture.WorldLocalFileName);
}

[Fact]
public void GetObject()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Apis.Storage.v1;
using Google.Apis.Upload;
using System;
using System.IO;
Expand All @@ -23,6 +24,46 @@ namespace Google.Cloud.Storage.V1
{
public abstract partial class StorageClient
{
/// <summary>
/// Creates an instance which is capable of starting a resumable upload for an object.
/// </summary>
/// <param name="bucket">The name of the bucket containing the object. Must not be null.</param>
/// <param name="objectName">The name of the object within the bucket. Must not be null.</param>
/// <param name="contentType">The content type of the object. This should be a MIME type
/// such as "text/html" or "application/octet-stream". May be null.</param>
/// <param name="source">The stream to read the data from. Must not be null.</param>
/// <param name="options">Additional options for the upload. May be null, in which case appropriate
/// defaults will be used.</param>
/// <returns>The <see cref="ObjectsResource.InsertMediaUpload"/> which can be used to upload the object.</returns>
/// <seealso cref="UploadObject(Object,Stream,UploadObjectOptions,IProgress{IUploadProgress})"/>
public virtual ObjectsResource.InsertMediaUpload CreateObjectUploader(
string bucket,
string objectName,
string contentType,
Stream source,
UploadObjectOptions options = null)
{
throw new NotImplementedException();
}

/// <summary>
/// Creates an instance which is capable of starting a resumable upload for an object.
/// </summary>
/// <param name="destination">Object to create or update. Must not be null, and must have the name,
/// bucket and content type populated.</param>
/// <param name="source">The stream to read the data from. Must not be null.</param>
/// <param name="options">Additional options for the upload. May be null, in which case appropriate
/// defaults will be used.</param>
/// <returns>The <see cref="ObjectsResource.InsertMediaUpload"/> which can be used to upload the object.</returns>
/// <seealso cref="UploadObject(Object,Stream,UploadObjectOptions,IProgress{IUploadProgress})"/>
public virtual ObjectsResource.InsertMediaUpload CreateObjectUploader(
Object destination,
Stream source,
UploadObjectOptions options = null)
{
throw new NotImplementedException();
}

/// <summary>
/// Uploads the data for an object in storage synchronously, from a specified stream.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using Google.Api.Gax;
using Google.Apis.Storage.v1;
using Google.Apis.Upload;
using System;
using System.IO;
Expand All @@ -24,6 +25,35 @@ namespace Google.Cloud.Storage.V1
{
public sealed partial class StorageClientImpl : StorageClient
{
/// <inheritdoc />
public override ObjectsResource.InsertMediaUpload CreateObjectUploader(
string bucket,
string objectName,
string contentType,
Stream source,
UploadObjectOptions options = null)
{
ValidateBucketName(bucket);
GaxPreconditions.CheckNotNull(objectName, nameof(objectName));
return CreateObjectUploader(
new Object { Bucket = bucket, Name = objectName, ContentType = contentType },
source, options);
}

/// <inheritdoc />
public override ObjectsResource.InsertMediaUpload CreateObjectUploader(
Object destination,
Stream source,
UploadObjectOptions options = null)
{
ValidateObject(destination, nameof(destination));
GaxPreconditions.CheckNotNull(source, nameof(source));
var mediaUpload = new CustomMediaUpload(Service, destination, destination.Bucket, source, destination.ContentType);
options?.ModifyMediaUpload(mediaUpload);
ApplyEncryptionKey(options?.EncryptionKey, mediaUpload);
return mediaUpload;
}

/// <inheritdoc />
public override Object UploadObject(
string bucket,
Expand Down Expand Up @@ -63,11 +93,7 @@ public override Object UploadObject(
UploadObjectOptions options = null,
IProgress<IUploadProgress> progress = null)
{
ValidateObject(destination, nameof(destination));
GaxPreconditions.CheckNotNull(source, nameof(source));
var mediaUpload = new CustomMediaUpload(Service, destination, destination.Bucket, source, destination.ContentType);
options?.ModifyMediaUpload(mediaUpload);
ApplyEncryptionKey(options?.EncryptionKey, mediaUpload);
var mediaUpload = CreateObjectUploader(destination, source, options);
if (progress != null)
{
mediaUpload.ProgressChanged += progress.Report;
Expand All @@ -89,11 +115,7 @@ public override async Task<Object> UploadObjectAsync(
CancellationToken cancellationToken = default(CancellationToken),
IProgress<IUploadProgress> progress = null)
{
ValidateObject(destination, nameof(destination));
GaxPreconditions.CheckNotNull(source, nameof(source));
var mediaUpload = new CustomMediaUpload(Service, destination, destination.Bucket, source, destination.ContentType);
options?.ModifyMediaUpload(mediaUpload);
ApplyEncryptionKey(options?.EncryptionKey, mediaUpload);
var mediaUpload = CreateObjectUploader(destination, source, options);
if (progress != null)
{
mediaUpload.ProgressChanged += progress.Report;
Expand Down
14 changes: 14 additions & 0 deletions apis/Google.Cloud.Storage.V1/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ Or write-only access to put specific object content into a bucket:

[!code-cs[](obj/snippets/Google.Cloud.Storage.V1.UrlSigner.txt#SignedURLPut)]

## Upload URIs

In some cases, it may not make sense for client applications to have permissions
to begin an upload for an object, but an authenticated service may choose to grant
this ability for individual uploads. Signed URLs are one option for this. Another
option is for the service to start a resumable upload session, but instead of
performing the upload, sending the resulting upload URI to the client application
so it can perform the upload instead. Unlike sessions initiated with a signed URL,
a pre-initated upload session will force the client application to upload through
the region in which the session began, which will likely be close to the service,
and not necessarily the client.

[!code-cs[](obj/snippets/Google.Cloud.Storage.V1.StorageClient.txt#UploadObjectWithSessionUri)]

## Customer-supplied encryption keys

Storage objects are always stored encrypted, but if you wish to
Expand Down