diff --git a/lib/storage/bucket.js b/lib/storage/bucket.js
index c6c554f64e0..a09caa72afc 100644
--- a/lib/storage/bucket.js
+++ b/lib/storage/bucket.js
@@ -875,6 +875,8 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
* will be uploaded to the File object's bucket and under the File object's
* name. Lastly, when this argument is omitted, the file is uploaded to your
* bucket using the name of the local file.
+ * @param {boolean} options.gzip - Automatically gzip the file. This will set
+ * `options.metadata.contentEncoding` to `gzip`.
* @param {object=} options.metadata - Metadata to set for your file.
* @param {boolean=} options.resumable - Force a resumable upload. (default:
* true for files larger than 5MB). Read more about resumable uploads
@@ -927,6 +929,17 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
* });
*
* //-
+ * // You can also have a file gzip'd on the fly.
+ * //-
+ * bucket.upload('index.html', { gzip: true }, function(err, file) {
+ * // Your bucket now contains:
+ * // - "index.html" (automatically compressed with gzip)
+ *
+ * // Downloading the file with `file.download` will automatically decode the
+ * // file.
+ * });
+ *
+ * //-
* // You may also re-use a File object, {module:storage/file}, that references
* // the file you wish to create or overwrite.
* //-
@@ -990,7 +1003,8 @@ Bucket.prototype.upload = function(localPath, options, callback) {
.pipe(newFile.createWriteStream({
validation: options.validation,
resumable: resumable,
- metadata: metadata
+ metadata: metadata,
+ gzip: options.gzip
}))
.on('error', function(err) {
callback(err);
diff --git a/lib/storage/file.js b/lib/storage/file.js
index 5e6b940b236..833ff6dc0aa 100644
--- a/lib/storage/file.js
+++ b/lib/storage/file.js
@@ -34,6 +34,7 @@ var request = require('request').defaults({
});
var streamEvents = require('stream-events');
var through = require('through2');
+var zlib = require('zlib');
/**
* @type {module:storage/acl}
@@ -611,8 +612,10 @@ File.prototype.createReadStream = function(options) {
* uploaded.
*
* @param {object=} options - Configuration object.
- * @param {object=} options.metadata - Set the metadata for this file.
- * @param {boolean=} options.resumable - Force a resumable upload. NOTE: When
+ * @param {boolean} options.gzip - Automatically gzip the file. This will set
+ * `options.metadata.contentEncoding` to `gzip`.
+ * @param {object} options.metadata - Set the metadata for this file.
+ * @param {boolean} options.resumable - Force a resumable upload. NOTE: When
* working with streams, the file format and size is unknown until it's
* completely consumed. Because of this, it's best for you to be explicit
* for what makes sense given your input. Read more about resumable uploads
@@ -642,6 +645,24 @@ File.prototype.createReadStream = function(options) {
* });
*
* //-
+ * //
Uploading a File with gzip compression
+ * //-
+ * var fs = require('fs');
+ * var htmlFile = myBucket.file('index.html');
+ *
+ * fs.createReadStream('/Users/stephen/site/index.html')
+ * .pipe(htmlFile.createWriteStream({ gzip: true }))
+ * .on('error', function(err) {})
+ * .on('complete', function(metadata) {
+ * // The file upload is complete.
+ * });
+ *
+ * //-
+ * // Downloading the file with `createReadStream` will automatically decode the
+ * // file.
+ * //-
+ *
+ * //-
* // Uploading a File with Metadata
* //
* // One last case you may run into is when you want to upload a file to your
@@ -661,13 +682,23 @@ File.prototype.createReadStream = function(options) {
* }
* }
* }))
- * .on('error', function(err) {});
+ * .on('error', function(err) {})
+ * .on('complete', function(metadata) {
+ * // The file upload is complete.
+ * });
*/
File.prototype.createWriteStream = function(options) {
options = options || {};
var that = this;
+
+ var gzip = options.gzip;
+
var metadata = options.metadata || {};
+ if (gzip) {
+ metadata.contentEncoding = 'gzip';
+ }
+
var validations = ['crc32c', 'md5'];
var validation;
@@ -693,9 +724,11 @@ File.prototype.createWriteStream = function(options) {
var localCrc32cHash;
var localMd5Hash = crypto.createHash('md5');
- var dup = streamEvents(duplexify());
+ var writableStream = streamEvents(duplexify());
- var throughStream = through(function(chunk, enc, next) {
+ var throughStream = through();
+
+ var validationStream = through(function(chunk, enc, next) {
if (crc32c) {
localCrc32cHash = crc.calculate(chunk, localCrc32cHash);
}
@@ -708,25 +741,30 @@ File.prototype.createWriteStream = function(options) {
next();
});
+ validationStream.on('end', function() {
+ if (crc32c) {
+ localCrc32cHash = new Buffer([localCrc32cHash]).toString('base64');
+ }
+
+ if (md5) {
+ localMd5Hash = localMd5Hash.digest('base64');
+ }
+ });
+
throughStream
- .on('end', function() {
- if (crc32c) {
- localCrc32cHash = new Buffer([localCrc32cHash]).toString('base64');
- }
- if (md5) {
- localMd5Hash = localMd5Hash.digest('base64');
- }
- })
+ .pipe(gzip ? zlib.createGzip() : through())
+
+ .pipe(validationStream)
- .pipe(dup)
+ .pipe(writableStream)
// Wait until we've received data to determine what upload technique to use.
.once('writing', function() {
- if (util.is(options.resumable, 'boolean') && !options.resumable) {
- that.startSimpleUpload_(dup, metadata);
+ if (options.resumable === false) {
+ that.startSimpleUpload_(writableStream, metadata);
} else {
- that.startResumableUpload_(dup, metadata);
+ that.startResumableUpload_(writableStream, metadata);
}
})
diff --git a/system-test/data/long-html-file.html b/system-test/data/long-html-file.html
index 1eda297b8dc..f8682dc7ae3 100644
Binary files a/system-test/data/long-html-file.html and b/system-test/data/long-html-file.html differ
diff --git a/system-test/data/long-html-file.html.gz b/system-test/data/long-html-file.html.gz
new file mode 100644
index 00000000000..1eda297b8dc
Binary files /dev/null and b/system-test/data/long-html-file.html.gz differ
diff --git a/system-test/storage.js b/system-test/storage.js
index 735977f5a60..66926e2b3b0 100644
--- a/system-test/storage.js
+++ b/system-test/storage.js
@@ -42,8 +42,11 @@ var files = {
big: {
path: 'system-test/data/three-mb-file.tif'
},
- gzip: {
+ html: {
path: 'system-test/data/long-html-file.html'
+ },
+ gzip: {
+ path: 'system-test/data/long-html-file.html.gz'
}
};
@@ -535,6 +538,24 @@ describe('storage', function() {
});
});
+ it('should gzip a file on the fly and download it', function(done) {
+ var options = {
+ gzip: true
+ };
+
+ var expectedContents = fs.readFileSync(files.html.path, 'utf-8');
+
+ bucket.upload(files.html.path, options, function(err, file) {
+ assert.ifError(err);
+
+ file.download(function(err, contents) {
+ assert.ifError(err);
+ assert.strictEqual(contents.toString(), expectedContents);
+ file.delete(done);
+ });
+ });
+ });
+
it('should upload a gzipped file and download it', function(done) {
var options = {
metadata: {
@@ -542,9 +563,16 @@ describe('storage', function() {
}
};
+ var expectedContents = fs.readFileSync(files.html.path, 'utf-8');
+
bucket.upload(files.gzip.path, options, function(err, file) {
assert.ifError(err);
- file.download(done);
+
+ file.download(function(err, contents) {
+ assert.ifError(err);
+ assert.strictEqual(contents.toString(), expectedContents);
+ file.delete(done);
+ });
});
});
diff --git a/test/storage/bucket.js b/test/storage/bucket.js
index 887b546b52a..e8fae1e0cae 100644
--- a/test/storage/bucket.js
+++ b/test/storage/bucket.js
@@ -960,6 +960,21 @@ describe('Bucket', function() {
bucket.upload(filepath, options, assert.ifError);
});
+ it('should allow specifying options.gzip', function(done) {
+ var fakeFile = new FakeFile(bucket, 'file-name');
+ var options = { destination: fakeFile, gzip: true };
+ fakeFile.createWriteStream = function(options) {
+ var ws = new stream.Writable();
+ ws.write = util.noop;
+ setImmediate(function() {
+ assert.strictEqual(options.gzip, true);
+ done();
+ });
+ return ws;
+ };
+ bucket.upload(filepath, options, assert.ifError);
+ });
+
it('should allow specifying options.resumable', function(done) {
var fakeFile = new FakeFile(bucket, 'file-name');
var options = { destination: fakeFile, resumable: false };
diff --git a/test/storage/file.js b/test/storage/file.js
index ac14eb5bdd7..7067f4feb30 100644
--- a/test/storage/file.js
+++ b/test/storage/file.js
@@ -920,6 +920,17 @@ describe('File', function() {
writable.write('data');
});
+ it('should set metadata.contentEncoding with gzip', function(done) {
+ var writable = file.createWriteStream({ gzip: true });
+
+ file.startResumableUpload_ = function(stream, metadata) {
+ assert.strictEqual(metadata.contentEncoding, 'gzip');
+ done();
+ };
+
+ writable.write('data');
+ });
+
describe('validation', function() {
var data = 'test';