Skip to content

Commit

Permalink
Merge pull request #408 from stephenplusplus/spp--storage-combine
Browse files Browse the repository at this point in the history
storage: support file.compose - fixes #401
  • Loading branch information
ryanseys committed Feb 24, 2015
2 parents 6938342 + 8308106 commit 19ca624
Show file tree
Hide file tree
Showing 5 changed files with 381 additions and 4 deletions.
46 changes: 46 additions & 0 deletions lib/common/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,52 @@ function getType(value) {
return Object.prototype.toString.call(value).match(/\s(\w+)\]/)[1];
}

/**
* Used in an Array iterator usually, this will return the value of a property
* in an object by its name.
*
* @param {string} name - Name of the property to return.
* @return {function}
*
* @example
* var people = [
* {
* name: 'Stephen',
* origin: 'USA',
* beenToNYC: false
* },
* {
* name: 'Ryan',
* origin: 'Canada',
* beenToNYC: true
* }
* ];
*
* var names = people.map(prop('name'));
* // [
* // 'Stephen',
* // 'Ryan'
* // ]
*
* var peopleInUSA = people.filter(prop('beenToNYC'));
* // [
* // {
* // name: 'Ryan',
* // origin: 'Canada',
* // beenToNYC: true
* // }
* // ]
*/
function prop(name) {
return function(item) {
if (name in item) {
return item[name];
}
};
}

module.exports.prop = prop;

/**
* Assign a value to a property in an Array iterator.
*
Expand Down
94 changes: 94 additions & 0 deletions lib/storage/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,100 @@ function Bucket(storage, name) {
/* jshint ignore:end */
}

/**
* Combine mutliple files into one new file.
*
* @throws if a non-array is provided as sources argument.
* @throws if less than two sources are provided.
* @throws if no destination is provided.
* @throws if a content type cannot be determined for the destination file.
*
* @param {string[]|module:storage/file[]} sources - The source files that will
* be combined.
* @param {string|module:storage/file} destination - The file you would like the
* source files combined into.
* @param {function=} callback - The callback function.
*
* @example
* var 2013logs = bucket.file('2013-logs.txt');
* var 2014logs = bucket.file('2014-logs.txt');
*
* var allLogs = bucket.file('all-logs.txt');
*
* bucket.combine([
* 2013logs,
* 2014logs
* ], allLogs, function(err, newFile) {
* // newFile === allLogs
* });
*/
Bucket.prototype.combine = function(sources, destination, callback) {
if (!util.is(sources, 'array') || sources.length < 2) {
throw new Error('You must provide at least two source files.');
}

if (!destination) {
throw new Error('A destination file must be specified.');
}

var self = this;

sources = sources.map(convertToFile);
destination = convertToFile(destination);
callback = callback || util.noop;

if (!destination.metadata.contentType) {
var destinationContentType = mime.contentType(destination.name);

if (destinationContentType) {
destination.metadata.contentType = destinationContentType;
} else {
throw new Error(
'A content type could not be detected for the destination file.');
}
}

this.storage.makeAuthorizedRequest_({
method: 'POST',
uri: util.format('{base}/{destBucket}/o/{destFile}/compose', {
base: STORAGE_BASE_URL,
destBucket: destination.bucket.name,
destFile: encodeURIComponent(destination.name)
}),
json: {
destination: {
contentType: destination.metadata.contentType
},
sourceObjects: sources.map(function (source) {
var sourceObject = {
name: source.name
};

if (source.metadata && source.metadata.generation) {
sourceObject.generation = source.metadata.generation;
}

return sourceObject;
})
}
}, function(err) {
if (err) {
callback(err);
return;
}

callback(null, destination);
});

function convertToFile(file) {
if (file instanceof File) {
return file;
} else {
return self.file(file);
}
}
};

/**
* Delete the bucket.
*
Expand Down
46 changes: 43 additions & 3 deletions regression/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ var fs = require('fs');
var request = require('request');
var through = require('through2');
var tmp = require('tmp');
var util = require('../lib/common/util');
var uuid = require('node-uuid');

var prop = util.prop;

var env = require('./env.js');
var storage = require('../lib/storage')(env);

Expand All @@ -47,12 +50,14 @@ function deleteFiles(bucket, callback) {
callback(err);
return;
}
async.map(files, function(file, next) {
file.delete(next);
}, callback);
async.map(files, deleteFile, callback);
});
}

function deleteFile(file, callback) {
file.delete(callback);
}

function generateBucketName() {
return 'gcloud-test-bucket-temp-' + uuid.v1();
}
Expand Down Expand Up @@ -493,6 +498,41 @@ describe('storage', function() {
});
});

describe('combine files', function() {
it('should combine multiple files into one', function(done) {
var files = [
{ file: bucket.file('file-one.txt'), contents: '123' },
{ file: bucket.file('file-two.txt'), contents: '456' }
];

async.each(files, createFile, function(err) {
assert.ifError(err);

var sourceFiles = files.map(prop('file'));
var destinationFile = bucket.file('file-one-and-two.txt');

bucket.combine(sourceFiles, destinationFile, function(err) {
assert.ifError(err);

destinationFile.download(function(err, contents) {
assert.ifError(err);

assert.equal(contents, files.map(prop('contents')).join(''));

async.each(sourceFiles.concat([destinationFile]), deleteFile, done);
});
});
});

function createFile(fileObject, cb) {
var ws = fileObject.file.createWriteStream();
ws.on('error', cb);
ws.on('complete', cb.bind(null, null));
ws.end(fileObject.contents);
}
});
});

describe('list files', function() {
var filenames = ['CloudLogo1', 'CloudLogo2', 'CloudLogo3'];

Expand Down
12 changes: 12 additions & 0 deletions test/common/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,18 @@ describe('common/util', function() {
});
});

describe('prop', function() {
it('should return objects that match the property name', function() {
var people = [
{ name: 'Stephen', origin: 'USA', beenToNYC: false },
{ name: 'Ryan', origin: 'Canada', beenToNYC: true }
];

assert.deepEqual(people.map(util.prop('name')), ['Stephen', 'Ryan']);
assert.deepEqual(people.filter(util.prop('beenToNYC')), [people[1]]);
});
});

describe('propAssign', function() {
it('should assign a property and value to an object', function() {
var obj = {};
Expand Down
Loading

0 comments on commit 19ca624

Please sign in to comment.