Skip to content

Commit

Permalink
Merge pull request #429 from stephenplusplus/spp--bucket-makepublicpr…
Browse files Browse the repository at this point in the history
…ivate

allow making a bucket public/private
  • Loading branch information
ryanseys committed Mar 23, 2015
2 parents a419cc1 + cd1f3e0 commit 60f1ab2
Show file tree
Hide file tree
Showing 3 changed files with 725 additions and 4 deletions.
292 changes: 292 additions & 0 deletions lib/storage/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

'use strict';

var async = require('async');
var extend = require('extend');
var fs = require('fs');
var mime = require('mime-types');
Expand Down Expand Up @@ -409,6 +410,214 @@ Bucket.prototype.getMetadata = function(callback) {
}.bind(this));
};

/**
* Make the bucket listing private.
*
* You may also choose to make the contents of the bucket private by specifying
* `includeFiles: true`. This will automatically run
* {module:storage/file#makePrivate} for every file in the bucket.
*
* When specifying `includeFiles: true`, use `force: true` to delay execution of
* your callback until all files have been processed. By default, the callback
* is executed after the first error. Use `force` to queue such errors until all
* files have been procssed, after which they will be returned as an array as
* the first argument to your callback.
*
* NOTE: This may cause the process to be long-running and use a high number of
* requests. Use with caution.
*
* @param {object=} options - The configuration object.
* @param {boolean} options.includeFiles - Make each file in the bucket private.
* Default: `false`.
* @param {boolean} options.force - Queue errors occurred while making files
* private until all files have been processed.
* @param {function} callback - The callback function.
*
* @example
* //-
* // Make the bucket private.
* //-
* bucket.makePrivate(function(err) {});
*
* //-
* // Make the bucket and its contents private.
* //-
* var opts = {
* includeFiles: true
* };
*
* bucket.makePrivate(opts, function(err, files) {
* // `err`:
* // The first error to occur, otherwise null.
* //
* // `files`:
* // Array of files successfully made private in the bucket.
* });
*
* //-
* // Make the bucket and its contents private, using force to suppress errors
* // until all files have been processed.
* //-
* var opts = {
* includeFiles: true,
* force: true
* };
*
* bucket.makePrivate(opts, function(errors, files) {
* // `errors`:
* // Array of errors if any occurred, otherwise null.
* //
* // `files`:
* // Array of files successfully made private in the bucket.
* });
*/
Bucket.prototype.makePrivate = function(options, callback) {
var self = this;

if (util.is(options, 'function')) {
callback = options;
options = {};
}

options = options || {};
options.private = true;

async.parallel([setPredefinedAcl, makeFilesPrivate], callback);

function setPredefinedAcl(done) {
var query = {
predefinedAcl: 'projectPrivate'
};

// You aren't allowed to set both predefinedAcl & acl properties on a bucket
// so acl must explicitly be nullified.
var metadata = { acl: null };

self.makeReq_('PATCH', '', query, metadata, function(err, resp) {
if (err) {
done(err);
return;
}

self.metadata = resp;

done();
});
}

function makeFilesPrivate(done) {
if (!options.includeFiles) {
done();
return;
}

self.makeAllFilesPublicPrivate_(options, done);
}
};

/**
* Make the bucket publicly readable.
*
* You may also choose to make the contents of the bucket publicly readable by
* specifying `includeFiles: true`. This will automatically run
* {module:storage/file#makePublic} for every file in the bucket.
*
* When specifying `includeFiles: true`, use `force: true` to delay execution of
* your callback until all files have been processed. By default, the callback
* is executed after the first error. Use `force` to queue such errors until all
* files have been procssed, after which they will be returned as an array as
* the first argument to your callback.
*
* NOTE: This may cause the process to be long-running and use a high number of
* requests. Use with caution.
*
* @param {object=} options - The configuration object.
* @param {boolean} options.includeFiles - Make each file in the bucket publicly
* readable. Default: `false`.
* @param {boolean} options.force - Queue errors occurred while making files
* public until all files have been processed.
* @param {function} callback - The callback function.
*
* @example
* //-
* // Make the bucket publicly readable.
* //-
* bucket.makePublic(function(err) {});
*
* //-
* // Make the bucket and its contents publicly readable.
* //-
* var opts = {
* includeFiles: true
* };
*
* bucket.makePublic(opts, function(err, files) {
* // `err`:
* // The first error to occur, otherwise null.
* //
* // `files`:
* // Array of files successfully made public in the bucket.
* });
*
* //-
* // Make the bucket and its contents publicly readable, using force to
* // suppress errors until all files have been processed.
* //-
* var opts = {
* includeFiles: true,
* force: true
* };
*
* bucket.makePublic(opts, function(errors, files) {
* // `errors`:
* // Array of errors if any occurred, otherwise null.
* //
* // `files`:
* // Array of files successfully made public in the bucket.
* });
*/
Bucket.prototype.makePublic = function(options, callback) {
var self = this;

if (util.is(options, 'function')) {
callback = options;
options = {};
}

options = options || {};
options.public = true;

async.parallel([
addAclPermissions,
addDefaultAclPermissions,
makeFilesPublic
], callback);

function addAclPermissions(done) {
// Allow reading bucket contents while preserving original permissions.
self.acl.add({
entity: 'allUsers',
role: 'READER'
}, done);
}

function addDefaultAclPermissions(done) {
self.acl.default.add({
entity: 'allUsers',
role: 'READER'
}, done);
}

function makeFilesPublic(done) {
if (!options.includeFiles) {
done();
return;
}

self.makeAllFilesPublicPrivate_(options, done);
}
};

/**
* Set the bucket's metadata.
*
Expand Down Expand Up @@ -576,6 +785,89 @@ Bucket.prototype.upload = function(localPath, options, callback) {
}
};

/**
* Iterate over all of a bucket's files, calling `file.makePublic()` (public)
* or `file.makePrivate()` (private) on each.
*
* Operations are performed in parallel, up to 10 at once. The first error
* breaks the loop, and will execute the provided callback with it. Specify
* `{ force: true }` to suppress the errors.
*
* @private
*
* @param {object} options - Configuration object.
* @param {boolean} options.force - Supress errors until all files have been
* processed.
* @param {boolean} options.private - Make files private.
* @param {boolean} options.public - Make files public.
* @param {function} callback - The callback function.
*/
Bucket.prototype.makeAllFilesPublicPrivate_ = function(options, callback) {
var self = this;

var MAX_PARALLEL_LIMIT = 10;
var errors = [];
var updatedFiles = [];

// Start processing files, iteratively fetching more as necessary.
processFiles({}, function (err) {
if (err || errors.length > 0) {
callback(err || errors, updatedFiles);
return;
}

callback(null, updatedFiles);
});

function processFiles(query, callback) {
self.getFiles(query, function(err, files, nextQuery) {
if (err) {
callback(err);
return;
}

// Iterate through each file and make it public or private.
async.eachLimit(files, MAX_PARALLEL_LIMIT, processFile, function(err) {
if (err) {
callback(err);
return;
}

if (nextQuery) {
processFiles(nextQuery, callback);
return;
}

callback();
});
});
}

function processFile(file, callback) {
if (options.public) {
file.makePublic(processedCallback);
} else if (options.private) {
file.makePrivate(processedCallback);
}

function processedCallback(err) {
if (err) {
if (options.force) {
errors.push(err);
callback();
return;
}

callback(err);
return;
}

updatedFiles.push(file);
callback();
}
}
};

/**
* Make a new request object from the provided arguments and wrap the callback
* to intercept non-successful responses.
Expand Down
Loading

0 comments on commit 60f1ab2

Please sign in to comment.