Skip to content

Commit ef9a138

Browse files
storage: add gzip option
1 parent 7efd2a8 commit ef9a138

7 files changed

Lines changed: 126 additions & 20 deletions

File tree

lib/storage/bucket.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,8 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
875875
* will be uploaded to the File object's bucket and under the File object's
876876
* name. Lastly, when this argument is omitted, the file is uploaded to your
877877
* bucket using the name of the local file.
878+
* @param {boolean} options.gzip - Automatically gzip the file. This will set
879+
* `options.metadata.contentEncoding` to `gzip`.
878880
* @param {object=} options.metadata - Metadata to set for your file.
879881
* @param {boolean=} options.resumable - Force a resumable upload. (default:
880882
* true for files larger than 5MB). Read more about resumable uploads
@@ -927,6 +929,17 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
927929
* });
928930
*
929931
* //-
932+
* // You can also have a file gzip'd on the fly.
933+
* //-
934+
* bucket.upload('index.html', { gzip: true }, function(err, file) {
935+
* // Your bucket now contains:
936+
* // - "index.html" (automatically compressed with gzip)
937+
*
938+
* // Downloading the file with `file.download` will automatically decode the
939+
* // file.
940+
* });
941+
*
942+
* //-
930943
* // You may also re-use a File object, {module:storage/file}, that references
931944
* // the file you wish to create or overwrite.
932945
* //-
@@ -990,7 +1003,8 @@ Bucket.prototype.upload = function(localPath, options, callback) {
9901003
.pipe(newFile.createWriteStream({
9911004
validation: options.validation,
9921005
resumable: resumable,
993-
metadata: metadata
1006+
metadata: metadata,
1007+
gzip: options.gzip
9941008
}))
9951009
.on('error', function(err) {
9961010
callback(err);

lib/storage/file.js

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var request = require('request').defaults({
3434
});
3535
var streamEvents = require('stream-events');
3636
var through = require('through2');
37+
var zlib = require('zlib');
3738

3839
/**
3940
* @type {module:storage/acl}
@@ -611,8 +612,10 @@ File.prototype.createReadStream = function(options) {
611612
* uploaded.
612613
*
613614
* @param {object=} options - Configuration object.
614-
* @param {object=} options.metadata - Set the metadata for this file.
615-
* @param {boolean=} options.resumable - Force a resumable upload. NOTE: When
615+
* @param {boolean} options.gzip - Automatically gzip the file. This will set
616+
* `options.metadata.contentEncoding` to `gzip`.
617+
* @param {object} options.metadata - Set the metadata for this file.
618+
* @param {boolean} options.resumable - Force a resumable upload. NOTE: When
616619
* working with streams, the file format and size is unknown until it's
617620
* completely consumed. Because of this, it's best for you to be explicit
618621
* for what makes sense given your input. Read more about resumable uploads
@@ -642,6 +645,24 @@ File.prototype.createReadStream = function(options) {
642645
* });
643646
*
644647
* //-
648+
* // <h4>Uploading a File with gzip compression</h4>
649+
* //-
650+
* var fs = require('fs');
651+
* var htmlFile = myBucket.file('index.html');
652+
*
653+
* fs.createReadStream('/Users/stephen/site/index.html')
654+
* .pipe(htmlFile.createWriteStream({ gzip: true }))
655+
* .on('error', function(err) {})
656+
* .on('complete', function(metadata) {
657+
* // The file upload is complete.
658+
* });
659+
*
660+
* //-
661+
* // Downloading the file with `createReadStream` will automatically decode the
662+
* // file.
663+
* //-
664+
*
665+
* //-
645666
* // <h4>Uploading a File with Metadata</h4>
646667
* //
647668
* // 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) {
661682
* }
662683
* }
663684
* }))
664-
* .on('error', function(err) {});
685+
* .on('error', function(err) {})
686+
* .on('complete', function(metadata) {
687+
* // The file upload is complete.
688+
* });
665689
*/
666690
File.prototype.createWriteStream = function(options) {
667691
options = options || {};
668692

669693
var that = this;
694+
695+
var gzip = options.gzip;
696+
670697
var metadata = options.metadata || {};
698+
if (gzip) {
699+
metadata.contentEncoding = 'gzip';
700+
}
701+
671702
var validations = ['crc32c', 'md5'];
672703
var validation;
673704

@@ -693,9 +724,11 @@ File.prototype.createWriteStream = function(options) {
693724
var localCrc32cHash;
694725
var localMd5Hash = crypto.createHash('md5');
695726

696-
var dup = streamEvents(duplexify());
727+
var writableStream = streamEvents(duplexify());
697728

698-
var throughStream = through(function(chunk, enc, next) {
729+
var throughStream = through();
730+
731+
var validationStream = through(function(chunk, enc, next) {
699732
if (crc32c) {
700733
localCrc32cHash = crc.calculate(chunk, localCrc32cHash);
701734
}
@@ -708,25 +741,30 @@ File.prototype.createWriteStream = function(options) {
708741
next();
709742
});
710743

744+
validationStream.on('end', function() {
745+
if (crc32c) {
746+
localCrc32cHash = new Buffer([localCrc32cHash]).toString('base64');
747+
}
748+
749+
if (md5) {
750+
localMd5Hash = localMd5Hash.digest('base64');
751+
}
752+
});
753+
711754
throughStream
712-
.on('end', function() {
713-
if (crc32c) {
714-
localCrc32cHash = new Buffer([localCrc32cHash]).toString('base64');
715-
}
716755

717-
if (md5) {
718-
localMd5Hash = localMd5Hash.digest('base64');
719-
}
720-
})
756+
.pipe(gzip ? zlib.createGzip() : through())
757+
758+
.pipe(validationStream)
721759

722-
.pipe(dup)
760+
.pipe(writableStream)
723761

724762
// Wait until we've received data to determine what upload technique to use.
725763
.once('writing', function() {
726-
if (util.is(options.resumable, 'boolean') && !options.resumable) {
727-
that.startSimpleUpload_(dup, metadata);
764+
if (options.resumable === false) {
765+
that.startSimpleUpload_(writableStream, metadata);
728766
} else {
729-
that.startResumableUpload_(dup, metadata);
767+
that.startResumableUpload_(writableStream, metadata);
730768
}
731769
})
732770

67.4 KB
Binary file not shown.
17 KB
Binary file not shown.

system-test/storage.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ var files = {
4242
big: {
4343
path: 'system-test/data/three-mb-file.tif'
4444
},
45-
gzip: {
45+
html: {
4646
path: 'system-test/data/long-html-file.html'
47+
},
48+
gzip: {
49+
path: 'system-test/data/long-html-file.html.gz'
4750
}
4851
};
4952

@@ -535,16 +538,41 @@ describe('storage', function() {
535538
});
536539
});
537540

541+
it('should gzip a file on the fly and download it', function(done) {
542+
var options = {
543+
gzip: true
544+
};
545+
546+
var expectedContents = fs.readFileSync(files.html.path, 'utf-8');
547+
548+
bucket.upload(files.html.path, options, function(err, file) {
549+
assert.ifError(err);
550+
551+
file.download(function(err, contents) {
552+
assert.ifError(err);
553+
assert.strictEqual(contents.toString(), expectedContents);
554+
file.delete(done);
555+
});
556+
});
557+
});
558+
538559
it('should upload a gzipped file and download it', function(done) {
539560
var options = {
540561
metadata: {
541562
contentEncoding: 'gzip'
542563
}
543564
};
544565

566+
var expectedContents = fs.readFileSync(files.html.path, 'utf-8');
567+
545568
bucket.upload(files.gzip.path, options, function(err, file) {
546569
assert.ifError(err);
547-
file.download(done);
570+
571+
file.download(function(err, contents) {
572+
assert.ifError(err);
573+
assert.strictEqual(contents.toString(), expectedContents);
574+
file.delete(done);
575+
});
548576
});
549577
});
550578

test/storage/bucket.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,21 @@ describe('Bucket', function() {
960960
bucket.upload(filepath, options, assert.ifError);
961961
});
962962

963+
it('should allow specifying options.gzip', function(done) {
964+
var fakeFile = new FakeFile(bucket, 'file-name');
965+
var options = { destination: fakeFile, gzip: true };
966+
fakeFile.createWriteStream = function(options) {
967+
var ws = new stream.Writable();
968+
ws.write = util.noop;
969+
setImmediate(function() {
970+
assert.strictEqual(options.gzip, true);
971+
done();
972+
});
973+
return ws;
974+
};
975+
bucket.upload(filepath, options, assert.ifError);
976+
});
977+
963978
it('should allow specifying options.resumable', function(done) {
964979
var fakeFile = new FakeFile(bucket, 'file-name');
965980
var options = { destination: fakeFile, resumable: false };

test/storage/file.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,17 @@ describe('File', function() {
920920
writable.write('data');
921921
});
922922

923+
it('should set metadata.contentEncoding with gzip', function(done) {
924+
var writable = file.createWriteStream({ gzip: true });
925+
926+
file.startResumableUpload_ = function(stream, metadata) {
927+
assert.strictEqual(metadata.contentEncoding, 'gzip');
928+
done();
929+
};
930+
931+
writable.write('data');
932+
});
933+
923934
describe('validation', function() {
924935
var data = 'test';
925936

0 commit comments

Comments
 (0)