|
20 | 20 |
|
21 | 21 | 'use strict'; |
22 | 22 |
|
| 23 | +var async = require('async'); |
23 | 24 | var extend = require('extend'); |
24 | 25 | var fs = require('fs'); |
25 | 26 | var mime = require('mime-types'); |
@@ -409,6 +410,214 @@ Bucket.prototype.getMetadata = function(callback) { |
409 | 410 | }.bind(this)); |
410 | 411 | }; |
411 | 412 |
|
| 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 | + |
412 | 621 | /** |
413 | 622 | * Set the bucket's metadata. |
414 | 623 | * |
@@ -576,6 +785,89 @@ Bucket.prototype.upload = function(localPath, options, callback) { |
576 | 785 | } |
577 | 786 | }; |
578 | 787 |
|
| 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 | + |
579 | 871 | /** |
580 | 872 | * Make a new request object from the provided arguments and wrap the callback |
581 | 873 | * to intercept non-successful responses. |
|
0 commit comments