Skip to content

Commit 60f1ab2

Browse files
committed
Merge pull request #429 from stephenplusplus/spp--bucket-makepublicprivate
allow making a bucket public/private
2 parents a419cc1 + cd1f3e0 commit 60f1ab2

3 files changed

Lines changed: 725 additions & 4 deletions

File tree

lib/storage/bucket.js

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
'use strict';
2222

23+
var async = require('async');
2324
var extend = require('extend');
2425
var fs = require('fs');
2526
var mime = require('mime-types');
@@ -409,6 +410,214 @@ Bucket.prototype.getMetadata = function(callback) {
409410
}.bind(this));
410411
};
411412

413+
/**
414+
* Make the bucket listing private.
415+
*
416+
* You may also choose to make the contents of the bucket private by specifying
417+
* `includeFiles: true`. This will automatically run
418+
* {module:storage/file#makePrivate} for every file in the bucket.
419+
*
420+
* When specifying `includeFiles: true`, use `force: true` to delay execution of
421+
* your callback until all files have been processed. By default, the callback
422+
* is executed after the first error. Use `force` to queue such errors until all
423+
* files have been procssed, after which they will be returned as an array as
424+
* the first argument to your callback.
425+
*
426+
* NOTE: This may cause the process to be long-running and use a high number of
427+
* requests. Use with caution.
428+
*
429+
* @param {object=} options - The configuration object.
430+
* @param {boolean} options.includeFiles - Make each file in the bucket private.
431+
* Default: `false`.
432+
* @param {boolean} options.force - Queue errors occurred while making files
433+
* private until all files have been processed.
434+
* @param {function} callback - The callback function.
435+
*
436+
* @example
437+
* //-
438+
* // Make the bucket private.
439+
* //-
440+
* bucket.makePrivate(function(err) {});
441+
*
442+
* //-
443+
* // Make the bucket and its contents private.
444+
* //-
445+
* var opts = {
446+
* includeFiles: true
447+
* };
448+
*
449+
* bucket.makePrivate(opts, function(err, files) {
450+
* // `err`:
451+
* // The first error to occur, otherwise null.
452+
* //
453+
* // `files`:
454+
* // Array of files successfully made private in the bucket.
455+
* });
456+
*
457+
* //-
458+
* // Make the bucket and its contents private, using force to suppress errors
459+
* // until all files have been processed.
460+
* //-
461+
* var opts = {
462+
* includeFiles: true,
463+
* force: true
464+
* };
465+
*
466+
* bucket.makePrivate(opts, function(errors, files) {
467+
* // `errors`:
468+
* // Array of errors if any occurred, otherwise null.
469+
* //
470+
* // `files`:
471+
* // Array of files successfully made private in the bucket.
472+
* });
473+
*/
474+
Bucket.prototype.makePrivate = function(options, callback) {
475+
var self = this;
476+
477+
if (util.is(options, 'function')) {
478+
callback = options;
479+
options = {};
480+
}
481+
482+
options = options || {};
483+
options.private = true;
484+
485+
async.parallel([setPredefinedAcl, makeFilesPrivate], callback);
486+
487+
function setPredefinedAcl(done) {
488+
var query = {
489+
predefinedAcl: 'projectPrivate'
490+
};
491+
492+
// You aren't allowed to set both predefinedAcl & acl properties on a bucket
493+
// so acl must explicitly be nullified.
494+
var metadata = { acl: null };
495+
496+
self.makeReq_('PATCH', '', query, metadata, function(err, resp) {
497+
if (err) {
498+
done(err);
499+
return;
500+
}
501+
502+
self.metadata = resp;
503+
504+
done();
505+
});
506+
}
507+
508+
function makeFilesPrivate(done) {
509+
if (!options.includeFiles) {
510+
done();
511+
return;
512+
}
513+
514+
self.makeAllFilesPublicPrivate_(options, done);
515+
}
516+
};
517+
518+
/**
519+
* Make the bucket publicly readable.
520+
*
521+
* You may also choose to make the contents of the bucket publicly readable by
522+
* specifying `includeFiles: true`. This will automatically run
523+
* {module:storage/file#makePublic} for every file in the bucket.
524+
*
525+
* When specifying `includeFiles: true`, use `force: true` to delay execution of
526+
* your callback until all files have been processed. By default, the callback
527+
* is executed after the first error. Use `force` to queue such errors until all
528+
* files have been procssed, after which they will be returned as an array as
529+
* the first argument to your callback.
530+
*
531+
* NOTE: This may cause the process to be long-running and use a high number of
532+
* requests. Use with caution.
533+
*
534+
* @param {object=} options - The configuration object.
535+
* @param {boolean} options.includeFiles - Make each file in the bucket publicly
536+
* readable. Default: `false`.
537+
* @param {boolean} options.force - Queue errors occurred while making files
538+
* public until all files have been processed.
539+
* @param {function} callback - The callback function.
540+
*
541+
* @example
542+
* //-
543+
* // Make the bucket publicly readable.
544+
* //-
545+
* bucket.makePublic(function(err) {});
546+
*
547+
* //-
548+
* // Make the bucket and its contents publicly readable.
549+
* //-
550+
* var opts = {
551+
* includeFiles: true
552+
* };
553+
*
554+
* bucket.makePublic(opts, function(err, files) {
555+
* // `err`:
556+
* // The first error to occur, otherwise null.
557+
* //
558+
* // `files`:
559+
* // Array of files successfully made public in the bucket.
560+
* });
561+
*
562+
* //-
563+
* // Make the bucket and its contents publicly readable, using force to
564+
* // suppress errors until all files have been processed.
565+
* //-
566+
* var opts = {
567+
* includeFiles: true,
568+
* force: true
569+
* };
570+
*
571+
* bucket.makePublic(opts, function(errors, files) {
572+
* // `errors`:
573+
* // Array of errors if any occurred, otherwise null.
574+
* //
575+
* // `files`:
576+
* // Array of files successfully made public in the bucket.
577+
* });
578+
*/
579+
Bucket.prototype.makePublic = function(options, callback) {
580+
var self = this;
581+
582+
if (util.is(options, 'function')) {
583+
callback = options;
584+
options = {};
585+
}
586+
587+
options = options || {};
588+
options.public = true;
589+
590+
async.parallel([
591+
addAclPermissions,
592+
addDefaultAclPermissions,
593+
makeFilesPublic
594+
], callback);
595+
596+
function addAclPermissions(done) {
597+
// Allow reading bucket contents while preserving original permissions.
598+
self.acl.add({
599+
entity: 'allUsers',
600+
role: 'READER'
601+
}, done);
602+
}
603+
604+
function addDefaultAclPermissions(done) {
605+
self.acl.default.add({
606+
entity: 'allUsers',
607+
role: 'READER'
608+
}, done);
609+
}
610+
611+
function makeFilesPublic(done) {
612+
if (!options.includeFiles) {
613+
done();
614+
return;
615+
}
616+
617+
self.makeAllFilesPublicPrivate_(options, done);
618+
}
619+
};
620+
412621
/**
413622
* Set the bucket's metadata.
414623
*
@@ -576,6 +785,89 @@ Bucket.prototype.upload = function(localPath, options, callback) {
576785
}
577786
};
578787

788+
/**
789+
* Iterate over all of a bucket's files, calling `file.makePublic()` (public)
790+
* or `file.makePrivate()` (private) on each.
791+
*
792+
* Operations are performed in parallel, up to 10 at once. The first error
793+
* breaks the loop, and will execute the provided callback with it. Specify
794+
* `{ force: true }` to suppress the errors.
795+
*
796+
* @private
797+
*
798+
* @param {object} options - Configuration object.
799+
* @param {boolean} options.force - Supress errors until all files have been
800+
* processed.
801+
* @param {boolean} options.private - Make files private.
802+
* @param {boolean} options.public - Make files public.
803+
* @param {function} callback - The callback function.
804+
*/
805+
Bucket.prototype.makeAllFilesPublicPrivate_ = function(options, callback) {
806+
var self = this;
807+
808+
var MAX_PARALLEL_LIMIT = 10;
809+
var errors = [];
810+
var updatedFiles = [];
811+
812+
// Start processing files, iteratively fetching more as necessary.
813+
processFiles({}, function (err) {
814+
if (err || errors.length > 0) {
815+
callback(err || errors, updatedFiles);
816+
return;
817+
}
818+
819+
callback(null, updatedFiles);
820+
});
821+
822+
function processFiles(query, callback) {
823+
self.getFiles(query, function(err, files, nextQuery) {
824+
if (err) {
825+
callback(err);
826+
return;
827+
}
828+
829+
// Iterate through each file and make it public or private.
830+
async.eachLimit(files, MAX_PARALLEL_LIMIT, processFile, function(err) {
831+
if (err) {
832+
callback(err);
833+
return;
834+
}
835+
836+
if (nextQuery) {
837+
processFiles(nextQuery, callback);
838+
return;
839+
}
840+
841+
callback();
842+
});
843+
});
844+
}
845+
846+
function processFile(file, callback) {
847+
if (options.public) {
848+
file.makePublic(processedCallback);
849+
} else if (options.private) {
850+
file.makePrivate(processedCallback);
851+
}
852+
853+
function processedCallback(err) {
854+
if (err) {
855+
if (options.force) {
856+
errors.push(err);
857+
callback();
858+
return;
859+
}
860+
861+
callback(err);
862+
return;
863+
}
864+
865+
updatedFiles.push(file);
866+
callback();
867+
}
868+
}
869+
};
870+
579871
/**
580872
* Make a new request object from the provided arguments and wrap the callback
581873
* to intercept non-successful responses.

0 commit comments

Comments
 (0)