From af681695e8c79eac2b2b51508f61b857af533817 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Wed, 15 Apr 2015 11:06:18 -0400 Subject: [PATCH] storage: support specifying a generation --- lib/storage/bucket.js | 7 ++- lib/storage/file.js | 106 +++++++++++++++++++++++++++++++++--------- test/storage/file.js | 13 +++--- 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/lib/storage/bucket.js b/lib/storage/bucket.js index c7db7235a5c..bf80fe9a13a 100644 --- a/lib/storage/bucket.js +++ b/lib/storage/bucket.js @@ -315,13 +315,16 @@ Bucket.prototype.delete = function(callback) { * the different use cases you may have. * * @param {string} name - The name of the file in this bucket. + * @param {object=} options - Configuration options. + * @param {string|number} options.generation - Only use a specific revision of + * this file. * @return {module:storage/file} * * @example * var file = bucket.file('my-existing-file.png'); */ -Bucket.prototype.file = function(name) { - return new File(this, name); +Bucket.prototype.file = function(name, options) { + return new File(this, name, options); }; /** diff --git a/lib/storage/file.js b/lib/storage/file.js index c5d7ca3407c..cb2ca2faacb 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -65,14 +65,17 @@ var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b'; * @alias module:storage/file * @constructor */ -function File(bucket, name, metadata) { +function File(bucket, name, options) { if (!name) { throw Error('A file name must be specified.'); } + options = options || {}; + this.bucket = bucket; + this.explicitGeneration = parseInt(options.generation, 10); this.makeReq_ = bucket.makeReq_.bind(bucket); - this.metadata = metadata || {}; + this.metadata = {}; Object.defineProperty(this, 'name', { enumerable: true, @@ -180,39 +183,55 @@ function File(bucket, name, metadata) { */ File.prototype.copy = function(destination, callback) { var noDestinationError = new Error('Destination file should have a name.'); + if (!destination) { throw noDestinationError; } + callback = callback || util.noop; + var destBucket; var destName; var newFile; + if (util.is(destination, 'string')) { destBucket = this.bucket; destName = destination; } + if (destination.constructor && destination.constructor.name === 'Bucket') { destBucket = destination; destName = this.name; } + if (destination instanceof File) { destBucket = destination.bucket; destName = destination.name; newFile = destination; } + if (!destName) { throw noDestinationError; } + var path = util.format('/o/{srcName}/copyTo/b/{destBucket}/o/{destName}', { srcName: encodeURIComponent(this.name), destBucket: destBucket.name, destName: encodeURIComponent(destName) }); - this.makeReq_('POST', path, null, {}, function(err) { + + var query = {}; + + if (this.explicitGeneration) { + query.sourceGeneration = this.explicitGeneration; + } + + this.makeReq_('POST', path, query, {}, function(err) { if (err) { callback(err); return; } + callback(null, newFile || destBucket.file(destName)); }); }; @@ -330,6 +349,12 @@ File.prototype.createReadStream = function(options) { uri: uri }; + if (that.explicitGeneration) { + reqOpts.qs = { + generation: that.explicitGeneration + }; + } + if (rangeRequest) { var start = util.is(options.start, 'number') ? options.start : '0'; var end = util.is(options.end, 'number') ? options.end : ''; @@ -641,14 +666,22 @@ File.prototype.createWriteStream = function(options) { */ File.prototype.delete = function(callback) { callback = callback || util.noop; + var path = '/o/' + encodeURIComponent(this.name); - this.makeReq_('DELETE', path, null, true, function(err) { + var query = {}; + + if (this.explicitGeneration) { + query.generation = this.explicitGeneration; + } + + this.makeReq_('DELETE', path, query, true, function(err) { if (err) { callback(err); return; } + callback(); - }.bind(this)); + }); }; /** @@ -717,15 +750,24 @@ File.prototype.download = function(options, callback) { */ File.prototype.getMetadata = function(callback) { callback = callback || util.noop; + + var that = this; var path = '/o/' + encodeURIComponent(this.name); - this.makeReq_('GET', path, null, true, function(err, resp) { + var query = {}; + + if (this.explicitGeneration) { + query.generation = this.explicitGeneration; + } + + this.makeReq_('GET', path, query, null, function(err, resp) { if (err) { callback(err); return; } - this.metadata = resp; - callback(null, this.metadata); - }.bind(this)); + + that.metadata = resp; + callback(null, that.metadata); + }); }; /** @@ -811,11 +853,17 @@ File.prototype.getSignedUrl = function(options, callback) { * }, function(err, metadata) {}); */ File.prototype.setMetadata = function(metadata, callback) { + callback = callback || util.noop; + var that = this; var path = '/o/' + encodeURIComponent(this.name); - callback = callback || util.noop; + var query = {}; + + if (this.explicitGeneration) { + query.generation = this.explicitGeneration; + } - this.makeReq_('PATCH', path, null, metadata, function(err, resp) { + this.makeReq_('PATCH', path, query, metadata, function(err, resp) { if (err) { callback(err); return; @@ -961,7 +1009,7 @@ File.prototype.startResumableUpload_ = function(stream, metadata) { headers['X-Upload-Content-Type'] = metadata.contentType; } - makeAuthorizedRequest({ + var reqOpts = { method: 'POST', uri: util.format('{base}/{bucket}/o', { base: STORAGE_UPLOAD_BASE_URL, @@ -969,11 +1017,17 @@ File.prototype.startResumableUpload_ = function(stream, metadata) { }), qs: { name: that.name, - uploadType: 'resumable' + uploadType: 'resumable', }, headers: headers, json: metadata - }, function(err, res, body) { + }; + + if (that.explicitGeneration) { + reqOpts.qs.ifGenerationMatch = that.explicitGeneration; + } + + makeAuthorizedRequest(reqOpts, function(err, res, body) { if (err) { handleError(err); return; @@ -1171,18 +1225,24 @@ File.prototype.startResumableUpload_ = function(stream, metadata) { File.prototype.startSimpleUpload_ = function(stream, metadata) { var that = this; + var reqOpts = { + qs: { + name: that.name + }, + uri: util.format('{base}/{bucket}/o', { + base: STORAGE_UPLOAD_BASE_URL, + bucket: that.bucket.name + }) + }; + + if (this.explicitGeneration) { + reqOpts.qs.ifGenerationMatch = this.explicitGeneration; + } + util.makeWritableStream(stream, { makeAuthorizedRequest: that.bucket.storage.makeAuthorizedRequest_, metadata: metadata, - request: { - qs: { - name: that.name - }, - uri: util.format('{base}/{bucket}/o', { - base: STORAGE_UPLOAD_BASE_URL, - bucket: that.bucket.name - }) - } + request: reqOpts }, function(data) { that.metadata = data; diff --git a/test/storage/file.js b/test/storage/file.js index 857478ab290..1b5c9bae51a 100644 --- a/test/storage/file.js +++ b/test/storage/file.js @@ -137,10 +137,9 @@ describe('File', function() { assert.equal(file.name, FILE_NAME); }); - it('should assign metadata if provided', function() { - var metadata = { a: 'b', c: 'd' }; - var newFile = new File(bucket, FILE_NAME, metadata); - assert.deepEqual(newFile.metadata, metadata); + it('should accept specifying a generation', function() { + var file = new File(bucket, 'name', { generation: 2 }); + assert.equal(file.explicitGeneration, 2); }); }); @@ -774,7 +773,7 @@ describe('File', function() { file.makeReq_ = function(method, path, query, body) { assert.equal(method, 'DELETE'); assert.equal(path, '/o/' + FILE_NAME); - assert.strictEqual(query, null); + assert.deepEqual(query, {}); assert.strictEqual(body, true); done(); }; @@ -937,8 +936,8 @@ describe('File', function() { file.makeReq_ = function(method, path, query, body) { assert.equal(method, 'GET'); assert.equal(path, '/o/' + FILE_NAME); - assert.strictEqual(query, null); - assert.strictEqual(body, true); + assert.deepEqual(query, {}); + assert.strictEqual(body, null); done(); }; file.getMetadata();