Skip to content

Commit

Permalink
feat(storage): support gs:// copy/move dests (#1416)
Browse files Browse the repository at this point in the history
Fixes #1395
  • Loading branch information
zbjornson authored and stephenplusplus committed Jul 12, 2016
1 parent 3f01f61 commit 79e0e28
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 4 deletions.
52 changes: 48 additions & 4 deletions lib/storage/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ var STORAGE_DOWNLOAD_BASE_URL = 'https://storage.googleapis.com';
*/
var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b';

/**
* @const {RegExp}
* @private
*/
var GS_URL_REGEXP = /^gs\:\/\/([a-z0-9_\.\-]+)\/(.+)$/;

/*! Developer Documentation
*
* @param {module:storage/bucket} bucket - The Bucket instance this file is
Expand Down Expand Up @@ -284,7 +290,7 @@ nodeutil.inherits(File, ServiceObject);
/**
* Copy this file to another file. By default, this will copy the file to the
* same bucket, but you can choose to copy it to another Bucket by providing
* either a Bucket or File object.
* a Bucket or File object or a URL starting with "gs://".
*
* @resource [Objects: copy API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/copy}
*
Expand Down Expand Up @@ -321,6 +327,22 @@ nodeutil.inherits(File, ServiceObject);
* });
*
* //-
* // If you pass in a string starting with "gs://" for the destination, the
* // file is copied to the other bucket and under the new name provided.
* //-
* var newLocation = 'gs://another-bucket/my-image-copy.png';
* file.copy(newLocation, function(err, copiedFile, apiResponse) {
* // `my-bucket` still contains:
* // - "my-image.png"
* //
* // `another-bucket` now contains:
* // - "my-image-copy.png"
*
* // `copiedFile` is an instance of a File object that refers to your new
* // file.
* });
*
* //-
* // If you pass in a Bucket object, the file will be copied to that bucket
* // using the same name.
* //-
Expand Down Expand Up @@ -366,8 +388,14 @@ File.prototype.copy = function(destination, callback) {
var newFile;

if (is.string(destination)) {
destBucket = this.bucket;
destName = destination;
var parsedDestination = GS_URL_REGEXP.exec(destination);
if (parsedDestination !== null && parsedDestination.length === 3) {
destBucket = this.storage.bucket(parsedDestination[1]);
destName = parsedDestination[2];
} else {
destBucket = this.bucket;
destName = destination;
}
} else if (destination.constructor &&
destination.constructor.name === 'Bucket') {
destBucket = destination;
Expand Down Expand Up @@ -1467,7 +1495,7 @@ File.prototype.makePublic = function(callback) {
/**
* Move this file to another location. By default, this will move the file to
* the same bucket, but you can choose to move it to another Bucket by providing
* either a Bucket or File object.
* a Bucket or File object or a URL beginning with "gs://".
*
* **Warning**:
* There is currently no atomic `move` method in the Google Cloud Storage API,
Expand Down Expand Up @@ -1513,6 +1541,22 @@ File.prototype.makePublic = function(callback) {
* });
*
* //-
* // If you pass in a string starting with "gs://" for the destination, the
* // file is copied to the other bucket and under the new name provided.
* //-
* var newLocation = 'gs://another-bucket/my-image-new.png';
* file.move(newLocation, function(err, destinationFile, apiResponse) {
* // `my-bucket` no longer contains:
* // - "my-image.png"
* //
* // `another-bucket` now contains:
* // - "my-image-new.png"
*
* // `destinationFile` is an instance of a File object that refers to your
* // new file.
* });
*
* //-
* // If you pass in a Bucket object, the file will be moved to that bucket
* // using the same name.
* //-
Expand Down
29 changes: 29 additions & 0 deletions system-test/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,13 +849,42 @@ describe('storage', function() {
assert.ifError(err);

file.copy('CloudLogoCopy', function(err, copiedFile) {
assert.ifError(err);
async.parallel([
file.delete.bind(file),
copiedFile.delete.bind(copiedFile)
], done);
});
});
});

it('should copy to another bucket given a gs:// URL', function(done) {
var opts = { destination: 'CloudLogo' };
bucket.upload(FILES.logo.path, opts, function(err, file) {
assert.ifError(err);

var otherBucket = storage.bucket(generateName());
otherBucket.create(function(err) {
assert.ifError(err);

var destPath = 'gs://' + otherBucket.name + '/CloudLogoCopy';
file.copy(destPath, function(err) {
assert.ifError(err);

otherBucket.getFiles(function(err, files) {
assert.ifError(err);

assert.strictEqual(files.length, 1);
var newFile = files[0];

assert.strictEqual(newFile.name, 'CloudLogoCopy');

done();
});
});
});
});
});
});

describe('channels', function() {
Expand Down
13 changes: 13 additions & 0 deletions test/storage/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ describe('File', function() {
} else {
return (requestOverride || requestCached)(req);
}
},
bucket: function(name) {
return new Bucket(this, name);
}
};

Expand Down Expand Up @@ -311,6 +314,16 @@ describe('File', function() {
file.copy(newFileName);
});

it('should allow a "gs://..." string', function(done) {
var newFileName = 'gs://other-bucket/new-file-name.png';
var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
destBucket: 'other-bucket',
destName: 'new-file-name.png'
});
assertPathEquals(file, expectedPath, done);
file.copy(newFileName);
});

it('should allow a Bucket', function(done) {
var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
destBucket: BUCKET.name,
Expand Down

0 comments on commit 79e0e28

Please sign in to comment.