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: support file.compose - fixes #401 #408

Merged
merged 2 commits into from
Feb 24, 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
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

This comment was marked as spam.

* },
* {
* 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) {

This comment was marked as spam.

return function(item) {
if (name in item) {
return item[name];
}
};
}

module.exports.prop = prop;

This comment was marked as spam.


/**
* 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) {

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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;

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


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(

This comment was marked as spam.

This comment was marked as spam.

'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;

This comment was marked as spam.


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