Skip to content

Commit baae93f

Browse files
storage: add download() method
1 parent cd9487a commit baae93f

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

lib/storage/file.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var ConfigStore = require('configstore');
2525
var crc = require('fast-crc32c');
2626
var crypto = require('crypto');
2727
var duplexify = require('duplexify');
28+
var fs = require('fs');
2829
var request = require('request');
2930
var streamEvents = require('stream-events');
3031
var through = require('through2');
@@ -619,6 +620,59 @@ File.prototype.delete = function(callback) {
619620
}.bind(this));
620621
};
621622

623+
/**
624+
* Convenience method to download a file into memory or to a local destination.
625+
*
626+
* @param {object=} options - Optional configuration. The arguments match those
627+
* passed to {module:storage/file#createReadStream}.
628+
* @param {string} options.destination - Local file path to write the file's
629+
* contents to.
630+
* @param {function} callback - The callback function.
631+
*
632+
* @example
633+
* //-
634+
* // Download a file into memory.
635+
* //-
636+
* file.download(function(err, contents) {});
637+
*
638+
* //-
639+
* // Download a file to a local destination.
640+
* //-
641+
* file.download({
642+
* destination: '/Users/stephen/Desktop/file-backup.txt'
643+
* }, function(err) {});
644+
*/
645+
File.prototype.download = function(options, callback) {
646+
if (util.is(options, 'function')) {
647+
callback = options;
648+
options = {};
649+
}
650+
651+
var destination = options.destination;
652+
delete options.destination;
653+
654+
var fileStream = this.createReadStream(options);
655+
656+
if (destination) {
657+
fileStream
658+
.on('error', callback)
659+
.pipe(fs.createWriteStream(destination))
660+
.on('error', callback)
661+
.on('finish', callback);
662+
} else {
663+
var fileContents = new Buffer('');
664+
665+
fileStream
666+
.on('error', callback)
667+
.on('data', function(chunk) {
668+
fileContents = Buffer.concat([fileContents, chunk]);
669+
})
670+
.on('complete', function() {
671+
callback(null, fileContents);
672+
});
673+
}
674+
};
675+
622676
/**
623677
* Get the file's metadata.
624678
*

regression/storage.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,20 @@ describe('storage', function() {
362362
});
363363
});
364364

365+
it('should download a file to memory', function(done) {
366+
var fileContents = fs.readFileSync(files.big.path);
367+
368+
bucket.upload(files.big.path, function(err, file) {
369+
assert.ifError(err);
370+
371+
file.download(function(err, remoteContents) {
372+
assert.ifError();
373+
assert.equal(fileContents, remoteContents);
374+
done();
375+
});
376+
});
377+
});
378+
365379
describe('stream write', function() {
366380
it('should stream write, then remove file (3mb)', function(done) {
367381
var file = bucket.file('LargeFile');

test/storage/file.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ var crc = require('fast-crc32c');
2424
var crypto = require('crypto');
2525
var duplexify = require('duplexify');
2626
var extend = require('extend');
27+
var fs = require('fs');
2728
var mockery = require('mockery');
2829
var nodeutil = require('util');
2930
var request = require('request');
3031
var stream = require('stream');
3132
var through = require('through2');
33+
var tmp = require('tmp');
3234
var url = require('url');
3335
var util = require('../../lib/common/util');
3436

@@ -741,6 +743,127 @@ describe('File', function() {
741743
});
742744
});
743745

746+
describe('download', function() {
747+
var fileReadStream;
748+
749+
beforeEach(function() {
750+
fileReadStream = new stream.Readable();
751+
fileReadStream._read = util.noop;
752+
753+
fileReadStream.on('end', function() {
754+
fileReadStream.emit('complete');
755+
});
756+
757+
file.createReadStream = function() {
758+
return fileReadStream;
759+
};
760+
});
761+
762+
it('should accept just a callback', function(done) {
763+
fileReadStream._read = function() {
764+
done();
765+
};
766+
767+
file.download(assert.ifError);
768+
});
769+
770+
it('should accept an options object and callback', function(done) {
771+
fileReadStream._read = function() {
772+
done();
773+
};
774+
775+
file.download({}, assert.ifError);
776+
});
777+
778+
it('should pass the provided options to createReadStream', function(done) {
779+
var readOptions = { start: 100, end: 200 };
780+
781+
file.createReadStream = function(options) {
782+
assert.deepEqual(options, readOptions);
783+
done();
784+
return fileReadStream;
785+
};
786+
787+
file.download(readOptions, assert.ifError);
788+
});
789+
790+
describe('into memory', function() {
791+
it('should buffer a file into memory if no destination', function(done) {
792+
var fileContents = 'abcdefghijklmnopqrstuvwxyz';
793+
794+
fileReadStream._read = function() {
795+
this.push(fileContents);
796+
this.push(null);
797+
};
798+
799+
file.download(function(err, remoteFileContents) {
800+
assert.ifError(err);
801+
802+
assert.equal(fileContents, remoteFileContents);
803+
done();
804+
});
805+
});
806+
807+
it('execute callback with error', function(done) {
808+
var error = new Error('Error.');
809+
810+
fileReadStream._read = function() {
811+
this.emit('error', error);
812+
};
813+
814+
file.download(function(err) {
815+
assert.equal(err, error);
816+
done();
817+
});
818+
});
819+
});
820+
821+
describe('with destination', function() {
822+
it('should write the file to a destination if provided', function(done) {
823+
tmp.setGracefulCleanup();
824+
tmp.file(function _tempFileCreated(err, tmpFilePath) {
825+
assert.ifError(err);
826+
827+
var fileContents = 'abcdefghijklmnopqrstuvwxyz';
828+
829+
fileReadStream._read = function() {
830+
this.push(fileContents);
831+
this.push(null);
832+
};
833+
834+
file.download({ destination: tmpFilePath }, function(err) {
835+
assert.ifError(err);
836+
837+
fs.readFile(tmpFilePath, function(err, tmpFileContents) {
838+
assert.ifError(err);
839+
840+
assert.equal(fileContents, tmpFileContents);
841+
done();
842+
});
843+
});
844+
});
845+
});
846+
847+
it('should execute callback with error', function(done) {
848+
tmp.setGracefulCleanup();
849+
tmp.file(function _tempFileCreated(err, tmpFilePath) {
850+
assert.ifError(err);
851+
852+
var error = new Error('Error.');
853+
854+
fileReadStream._read = function() {
855+
this.emit('error', error);
856+
};
857+
858+
file.download({ destination: tmpFilePath }, function(err) {
859+
assert.equal(err, error);
860+
done();
861+
});
862+
});
863+
});
864+
});
865+
});
866+
744867
describe('getMetadata', function() {
745868
var metadata = { a: 'b', c: 'd' };
746869

0 commit comments

Comments
 (0)