Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storage: add download() method #381

Merged
merged 1 commit into from
Feb 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

.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);

This comment was marked as spam.

});

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