Skip to content

Commit

Permalink
storage: add download() method
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenplusplus committed Feb 9, 2015
1 parent 1b1f6b3 commit 132150c
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 0 deletions.
58 changes: 58 additions & 0 deletions lib/storage/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var ConfigStore = require('configstore');
var crc = require('fast-crc32c');
var crypto = require('crypto');
var duplexify = require('duplexify');
var fs = require('fs');
var once = require('once');
var request = require('request');
var streamEvents = require('stream-events');
var through = require('through2');
Expand Down Expand Up @@ -619,6 +621,62 @@ File.prototype.delete = function(callback) {
}.bind(this));
};

/**
* Convenience method to download a file into memory or to a local destination.
*
* @param {object=} options - Optional configuration. The arguments match those
* passed to {module:storage/file#createReadStream}.
* @param {string} options.destination - Local file path to write the file's
* contents to.
* @param {function} callback - The callback function.
*
* @example
* //-
* // Download a file into memory. The contents will be available as the second
* // argument in the demonstration below, `contents`.
* //-
* file.download(function(err, contents) {});
*
* //-
* // Download a file to a local destination.
* //-
* file.download({
* destination: '/Users/stephen/Desktop/file-backup.txt'
* }, function(err) {});
*/
File.prototype.download = function(options, callback) {
if (util.is(options, 'function')) {
callback = options;
options = {};
}

callback = once(callback);

var destination = options.destination;
delete options.destination;

var fileStream = this.createReadStream(options);

if (destination) {
fileStream
.on('error', callback)
.pipe(fs.createWriteStream(destination))
.on('error', callback)
.on('finish', callback);
} else {
var fileContents = new Buffer('');

fileStream
.on('error', callback)
.on('data', function(chunk) {
fileContents = Buffer.concat([fileContents, chunk]);
})
.on('complete', function() {
callback(null, fileContents);
});
}
};

/**
* Get the file's metadata.
*
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"google-service-account": "^1.0.3",
"mime-types": "^2.0.8",
"node-uuid": "^1.4.2",
"once": "^1.3.1",
"protobufjs": "^3.8.2",
"request": "^2.53.0",
"stream-events": "^1.0.1",
Expand Down
14 changes: 14 additions & 0 deletions regression/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,20 @@ describe('storage', function() {
});
});

it('should download a file to memory', function(done) {
var fileContents = fs.readFileSync(files.big.path);

bucket.upload(files.big.path, function(err, file) {
assert.ifError(err);

file.download(function(err, remoteContents) {
assert.ifError(err);
assert.equal(fileContents, remoteContents);
done();
});
});
});

describe('stream write', function() {
it('should stream write, then remove file (3mb)', function(done) {
var file = bucket.file('LargeFile');
Expand Down
134 changes: 134 additions & 0 deletions test/storage/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ var crc = require('fast-crc32c');
var crypto = require('crypto');
var duplexify = require('duplexify');
var extend = require('extend');
var fs = require('fs');
var mockery = require('mockery');
var nodeutil = require('util');
var request = require('request');
var stream = require('stream');
var through = require('through2');
var tmp = require('tmp');
var url = require('url');
var util = require('../../lib/common/util');

Expand Down Expand Up @@ -741,6 +743,138 @@ describe('File', function() {
});
});

describe('download', function() {
var fileReadStream;

beforeEach(function() {
fileReadStream = new stream.Readable();
fileReadStream._read = util.noop;

fileReadStream.on('end', function() {
fileReadStream.emit('complete');
});

file.createReadStream = function() {
return fileReadStream;
};
});

it('should accept just a callback', function(done) {
fileReadStream._read = function() {
done();
};

file.download(assert.ifError);
});

it('should accept an options object and callback', function(done) {
fileReadStream._read = function() {
done();
};

file.download({}, assert.ifError);
});

it('should pass the provided options to createReadStream', function(done) {
var readOptions = { start: 100, end: 200 };

file.createReadStream = function(options) {
assert.deepEqual(options, readOptions);
done();
return fileReadStream;
};

file.download(readOptions, assert.ifError);
});

it('should only execute callback once', function(done) {
fileReadStream._read = function() {
this.emit('error', new Error('Error.'));
this.emit('error', new Error('Error.'));
};

file.download(function() {
done();
});
});

describe('into memory', function() {
it('should buffer a file into memory if no destination', function(done) {
var fileContents = 'abcdefghijklmnopqrstuvwxyz';

fileReadStream._read = function() {
this.push(fileContents);
this.push(null);
};

file.download(function(err, remoteFileContents) {
assert.ifError(err);

assert.equal(fileContents, remoteFileContents);
done();
});
});

it('should execute callback with error', function(done) {
var error = new Error('Error.');

fileReadStream._read = function() {
this.emit('error', error);
};

file.download(function(err) {
assert.equal(err, error);
done();
});
});
});

describe('with destination', function() {
it('should write the file to a destination if provided', function(done) {
tmp.setGracefulCleanup();
tmp.file(function _tempFileCreated(err, tmpFilePath) {
assert.ifError(err);

var fileContents = 'abcdefghijklmnopqrstuvwxyz';

fileReadStream._read = function() {
this.push(fileContents);
this.push(null);
};

file.download({ destination: tmpFilePath }, function(err) {
assert.ifError(err);

fs.readFile(tmpFilePath, function(err, tmpFileContents) {
assert.ifError(err);

assert.equal(fileContents, tmpFileContents);
done();
});
});
});
});

it('should execute callback with error', function(done) {
tmp.setGracefulCleanup();
tmp.file(function _tempFileCreated(err, tmpFilePath) {
assert.ifError(err);

var error = new Error('Error.');

fileReadStream._read = function() {
this.emit('error', error);
};

file.download({ destination: tmpFilePath }, function(err) {
assert.equal(err, error);
done();
});
});
});
});
});

describe('getMetadata', function() {
var metadata = { a: 'b', c: 'd' };

Expand Down

0 comments on commit 132150c

Please sign in to comment.