"use strict";
/**
 * Copyright 2014-2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@google-cloud/common");
const paginator_1 = require("@google-cloud/paginator");
const promisify_1 = require("@google-cloud/promisify");
const arrify = require("arrify");
const async = require("async");
const extend = require("extend");
const fs = require("fs");
const mime = require("mime-types");
const path = require("path");
const snakeize = require('snakeize');
const acl_1 = require("./acl");
const file_1 = require("./file");
const iam_1 = require("./iam");
const notification_1 = require("./notification");
/**
 * The size of a file (in bytes) must be greater than this number to
 * automatically trigger a resumable upload.
 *
 * @const {number}
 * @private
 */
const RESUMABLE_THRESHOLD = 5000000;
/**
 * Create a Bucket object to interact with a Cloud Storage bucket.
 *
 * @class
 * @hideconstructor
 *
 * @param {Storage} storage A {@link Storage} instance.
 * @param {string} name The name of the bucket.
 * @param {object} [options] Configuration object.
 * @param {string} [options.userProject] User project.
 *
 * @example
 * const {Storage} = require('@google-cloud/storage');
 * const storage = new Storage();
 * const bucket = storage.bucket('albums');
 */
class Bucket extends common_1.ServiceObject {
    constructor(storage, name, options) {
        options = options || {};
        // Allow for "gs://"-style input, and strip any trailing slashes.
        name = name.replace(/^gs:\/\//, '').replace(/\/+$/, '');
        const requestQueryObject = {};
        const userProject = options.userProject;
        if (typeof userProject === 'string') {
            requestQueryObject.userProject = userProject;
        }
        const methods = {
            /**
             * Create a bucket.
             *
             * @method Bucket#create
             * @param {CreateBucketRequest} [metadata] Metadata to set for the bucket.
             * @param {CreateBucketCallback} [callback] Callback function.
             * @returns {Promise<CreateBucketResponse>}
             *
             * @example
             * const {Storage} = require('@google-cloud/storage');
             * const storage = new Storage();
             * const bucket = storage.bucket('albums');
             * bucket.create(function(err, bucket, apiResponse) {
             *   if (!err) {
             *     // The bucket was created successfully.
             *   }
             * });
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * bucket.create().then(function(data) {
             *   const bucket = data[0];
             *   const apiResponse = data[1];
             * });
             */
            create: {
                reqOpts: {
                    qs: requestQueryObject,
                },
            },
            /**
             * @typedef {object} DeleteBucketOptions Configuration options.
             * @param {string} [userProject] The ID of the project which will be
             *     billed for the request.
             */
            /**
             * @typedef {array} DeleteBucketResponse
             * @property {object} 0 The full API response.
             */
            /**
             * @callback DeleteBucketCallback
             * @param {?Error} err Request error, if any.
             * @param {object} apiResponse The full API response.
             */
            /**
             * Delete the bucket.
             *
             * @see [Buckets: delete API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/delete}
             *
             * @method Bucket#delete
             * @param {DeleteBucketOptions} [options] Configuration options.
             * @param {DeleteBucketCallback} [callback] Callback function.
             * @returns {Promise<DeleteBucketResponse>}
             *
             * @example
             * const {Storage} = require('@google-cloud/storage');
             * const storage = new Storage();
             * const bucket = storage.bucket('albums');
             * bucket.delete(function(err, apiResponse) {});
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * bucket.delete().then(function(data) {
             *   const apiResponse = data[0];
             * });
             *
             * @example <caption>include:samples/buckets.js</caption>
             * region_tag:storage_delete_bucket
             * Another example:
             */
            delete: {
                reqOpts: {
                    qs: requestQueryObject,
                },
            },
            /**
             * @typedef {object} BucketExistsOptions Configuration options for Bucket#exists().
             * @param {string} [userProject] The ID of the project which will be
             *     billed for the request.
             */
            /**
             * @typedef {array} BucketExistsResponse
             * @property {boolean} 0 Whether the {@link Bucket} exists.
             */
            /**
             * @callback BucketExistsCallback
             * @param {?Error} err Request error, if any.
             * @param {boolean} exists Whether the {@link Bucket} exists.
             */
            /**
             * Check if the bucket exists.
             *
             * @method Bucket#exists
             * @param {BucketExistsOptions} [options] Configuration options.
             * @param {BucketExistsCallback} [callback] Callback function.
             * @returns {Promise<BucketExistsResponse>}
             *
             * @example
             * const {Storage} = require('@google-cloud/storage');
             * const storage = new Storage();
             * const bucket = storage.bucket('albums');
             *
             * bucket.exists(function(err, exists) {});
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * bucket.exists().then(function(data) {
             *   const exists = data[0];
             * });
             */
            exists: {
                reqOpts: {
                    qs: requestQueryObject,
                },
            },
            /**
             * @typedef {object} [GetBucketOptions] Configuration options for Bucket#get()
             * @property {boolean} [autoCreate] Automatically create the object if
             *     it does not exist. Default: `false`
             * @property {string} [userProject] The ID of the project which will be
             *     billed for the request.
             */
            /**
             * @typedef {array} GetBucketResponse
             * @property {Bucket} 0 The {@link Bucket}.
             * @property {object} 1 The full API response.
             */
            /**
             * @callback GetBucketCallback
             * @param {?Error} err Request error, if any.
             * @param {Bucket} bucket The {@link Bucket}.
             * @param {object} apiResponse The full API response.
             */
            /**
             * Get a bucket if it exists.
             *
             * You may optionally use this to "get or create" an object by providing
             * an object with `autoCreate` set to `true`. Any extra configuration that
             * is normally required for the `create` method must be contained within
             * this object as well.
             *
             * @method Bucket#get
             * @param {GetBucketOptions} [options] Configuration options.
             * @param {GetBucketCallback} [callback] Callback function.
             * @returns {Promise<GetBucketResponse>}
             *
             * @example
             * const {Storage} = require('@google-cloud/storage');
             * const storage = new Storage();
             * const bucket = storage.bucket('albums');
             *
             * bucket.get(function(err, bucket, apiResponse) {
             *   // `bucket.metadata` has been populated.
             * });
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * bucket.get().then(function(data) {
             *   const bucket = data[0];
             *   const apiResponse = data[1];
             * });
             */
            get: {
                reqOpts: {
                    qs: requestQueryObject,
                },
            },
            /**
             * @typedef {array} GetBucketMetadataResponse
             * @property {object} 0 The bucket metadata.
             * @property {object} 1 The full API response.
             */
            /**
             * @callback GetBucketMetadataCallback
             * @param {?Error} err Request error, if any.
             * @param {object} metadata The bucket metadata.
             * @param {object} apiResponse The full API response.
             */
            /**
             * @typedef {object} GetBucketMetadataOptions Configuration options for Bucket#getMetadata().
             * @property {string} [userProject] The ID of the project which will be
             *     billed for the request.
             */
            /**
             * Get the bucket's metadata.
             *
             * To set metadata, see {@link Bucket#setMetadata}.
             *
             * @see [Buckets: get API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/get}
             *
             * @method Bucket#getMetadata
             * @param {GetBucketMetadataOptions} [options] Configuration options.
             * @param {GetBucketMetadataCallback} [callback] Callback function.
             * @returns {Promise<GetBucketMetadataResponse>}
             *
             * @example
             * const {Storage} = require('@google-cloud/storage');
             * const storage = new Storage();
             * const bucket = storage.bucket('albums');
             *
             * bucket.getMetadata(function(err, metadata, apiResponse) {});
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * bucket.getMetadata().then(function(data) {
             *   const metadata = data[0];
             *   const apiResponse = data[1];
             * });
             *
             * @example <caption>include:samples/requesterPays.js</caption>
             * region_tag:storage_get_requester_pays_status
             * Example of retrieving the requester pays status of a bucket:
             */
            getMetadata: {
                reqOpts: {
                    qs: requestQueryObject,
                },
            },
            /**
             * @typedef {object} SetBucketMetadataOptions Configuration options for Bucket#setMetadata().
             * @property {string} [userProject] The ID of the project which will be
             *     billed for the request.
             */
            /**
             * @typedef {array} SetBucketMetadataResponse
             * @property {object} apiResponse The full API response.
             */
            /**
             * @callback SetBucketMetadataCallback
             * @param {?Error} err Request error, if any.
             * @param {object} metadata The bucket metadata.
             */
            /**
             * Set the bucket's metadata.
             *
             * @see [Buckets: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch}
             *
             * @method Bucket#setMetadata
             * @param {object<string, *>} metadata The metadata you wish to set.
             * @param {SetBucketMetadataOptions} [options] Configuration options.
             * @param {SetBucketMetadataCallback} [callback] Callback function.
             * @returns {Promise<SetBucketMetadataResponse>}
             *
             * @example
             * const {Storage} = require('@google-cloud/storage');
             * const storage = new Storage();
             * const bucket = storage.bucket('albums');
             *
             * //-
             * // Set website metadata field on the bucket.
             * //-
             * const metadata = {
             *   website: {
             *     mainPageSuffix: 'http://example.com',
             *     notFoundPage: 'http://example.com/404.html'
             *   }
             * };
             *
             * bucket.setMetadata(metadata, function(err, apiResponse) {});
             *
             * //-
             * // Enable versioning for your bucket.
             * //-
             * bucket.setMetadata({
             *   versioning: {
             *     enabled: true
             *   }
             * }, function(err, apiResponse) {});
             *
             * //-
             * // Enable KMS encryption for objects within this bucket.
             * //-
             * bucket.setMetadata({
             *   encryption: {
             *     defaultKmsKeyName: 'projects/grape-spaceship-123/...'
             *   }
             * }, function(err, apiResponse) {});
             *
             * //-
             * // Set the default event-based hold value for new objects in this
             * // bucket.
             * //-
             * bucket.setMetadata({
             *   defaultEventBasedHold: true
             * }, function(err, apiResponse) {});
             *
             * //-
             * // Remove object lifecycle rules.
             * //-
             * bucket.setMetadata({
             *   lifecycle: null
             * }, function(err, apiResponse) {});
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * bucket.setMetadata(metadata).then(function(data) {
             *   const apiResponse = data[0];
             * });
             */
            setMetadata: {
                reqOpts: {
                    qs: requestQueryObject,
                },
            },
        };
        super({
            parent: storage,
            baseUrl: '/b',
            id: name,
            createMethod: storage.createBucket.bind(storage),
            methods,
        });
        this.name = name;
        this.storage = storage;
        this.userProject = options.userProject;
        this.acl = new acl_1.Acl({
            request: this.request.bind(this),
            pathPrefix: '/acl',
        });
        this.acl.default = new acl_1.Acl({
            request: this.request.bind(this),
            pathPrefix: '/defaultObjectAcl',
        });
        this.iam = new iam_1.Iam(this);
        this.getFilesStream = paginator_1.paginator.streamify('getFiles');
    }
    /**
     * @typedef {object} AddLifecycleRuleOptions Configuration options for Bucket#addLifecycleRule().
     * @property {string} [append=true] The new rules will be appended to any
     *     pre-existing rules.
     */
    /**
     * Add an object lifecycle management rule to the bucket.
     *
     * By default, an Object Lifecycle Management rule provided to this method
     * will be included to the existing policy. To replace all existing rules,
     * supply the `options` argument, setting `append` to `false`.
     *
     * @see [Object Lifecycle Management]{@link https://cloud.google.com/storage/docs/lifecycle}
     * @see [Buckets: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch}
     *
     * @param {LifecycleRule} rule The new lifecycle rule to be added to objects
     *     in this bucket.
     * @param {string} [rule.storageClass] When using the `setStorageClass`
     *     action, provide this option to dictate which storage class the object
     *     should update to.
     * @param {AddLifecycleRuleOptions} [options] Configuration object.
     * @param {boolean} [options.append=true] Append the new rule to the existing
     *     policy.
     * @param {SetBucketMetadataCallback} [callback] Callback function.
     * @returns {Promise<SetBucketMetadataResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * //-
     * // Automatically have an object deleted from this bucket once it is 3 years
     * // of age.
     * //-
     * bucket.addLifecycleRule({
     *   action: 'delete',
     *   condition: {
     *     age: 365 * 3 // Specified in days.
     *   }
     * }, function(err, apiResponse) {
     *   if (err) {
     *     // Error handling omitted.
     *   }
     *
     *   const lifecycleRules = bucket.metadata.lifecycle.rule;
     *
     *   // Iterate over the Object Lifecycle Management rules on this bucket.
     *   lifecycleRules.forEach(lifecycleRule => {});
     * });
     *
     * //-
     * // By default, the rule you provide will be added to the existing policy.
     * // Optionally, you can disable this behavior to replace all of the
     * // pre-existing rules.
     * //-
     * const options = {
     *   append: false
     * };
     *
     * bucket.addLifecycleRule({
     *   action: 'delete',
     *   condition: {
     *     age: 365 * 3 // Specified in days.
     *   }
     * }, options, function(err, apiResponse) {
     *   if (err) {
     *     // Error handling omitted.
     *   }
     *
     *   // All rules have been replaced with the new "delete" rule.
     *
     *   // Iterate over the Object Lifecycle Management rules on this bucket.
     *   lifecycleRules.forEach(lifecycleRule => {});
     * });
     *
     * //-
     * // For objects created before 2018, "downgrade" the storage class.
     * //-
     * bucket.addLifecycleRule({
     *   action: 'setStorageClass',
     *   storageClass: 'COLDLINE',
     *   condition: {
     *     createdBefore: new Date('2018')
     *   }
     * }, function(err, apiResponse) {});
     *
     * //-
     * // Delete objects created before 2016 which have the Coldline storage
     * // class.
     * //-
     * bucket.addLifecycleRule({
     *   action: 'delete',
     *   condition: {
     *     matchesStorageClass: [
     *       'COLDLINE'
     *     ],
     *     createdBefore: new Date('2016')
     *   }
     * }, function(err, apiResponse) {});
     */
    addLifecycleRule(rule, optionsOrCallback, callback) {
        let options;
        if (typeof optionsOrCallback === 'function') {
            callback = optionsOrCallback;
        }
        else if (optionsOrCallback) {
            options = optionsOrCallback;
        }
        options = options || {};
        callback = callback || common_1.util.noop;
        const newLifecycleRules = arrify(rule).map(rule => {
            if (typeof rule.action === 'object') {
                // This is a raw-formatted rule object, the way the API expects.
                // Just pass it through as-is.
                return rule;
            }
            const apiFormattedRule = {};
            apiFormattedRule.condition = {};
            apiFormattedRule.action = {
                type: rule.action,
            };
            // @TODO: Remove if the API becomes less picky.
            if (rule.action === 'delete') {
                apiFormattedRule.action.type = 'Delete';
            }
            if (rule.storageClass) {
                apiFormattedRule.action.storageClass = rule.storageClass;
            }
            for (const condition in rule.condition) {
                if (rule.condition[condition] instanceof Date) {
                    apiFormattedRule.condition[condition] = rule.condition[condition]
                        .toISOString()
                        .replace(/T.+$/, '');
                }
                else {
                    apiFormattedRule.condition[condition] = rule.condition[condition];
                }
            }
            return apiFormattedRule;
        });
        if (options.append === false) {
            this.setMetadata({ lifecycle: { rule: newLifecycleRules } }, callback);
            return;
        }
        // The default behavior appends the previously-defined lifecycle rules with
        // the new ones just passed in by the user.
        this.getMetadata((err, metadata) => {
            if (err) {
                callback(err);
                return;
            }
            const currentLifecycleRules = arrify(metadata.lifecycle && metadata.lifecycle.rule);
            this.setMetadata({
                lifecycle: {
                    rule: currentLifecycleRules.concat(newLifecycleRules),
                },
            }, callback);
        });
    }
    /**
     * @typedef {object} CombineOptions
     * @property {string} [kmsKeyName] Resource name of the Cloud KMS key, of
     *     the form
     *     `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`,
     *     that will be used to encrypt the object. Overwrites the object
     * metadata's `kms_key_name` value, if any.
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @callback CombineCallback
     * @param {?Error} err Request error, if any.
     * @param {File} newFile The new {@link File}.
     * @param {object} apiResponse The full API response.
     */
    /**
     * @typedef {array} CombineResponse
     * @property {File} 0 The new {@link File}.
     * @property {object} 1 The full API response.
     */
    /**
     * Combine multiple files into one new file.
     *
     * @see [Objects: compose API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/compose}
     *
     * @throws {Error} if a non-array is provided as sources argument.
     * @throws {Error} if less than two sources are provided.
     * @throws {Error} if no destination is provided.
     *
     * @param {string[]|File[]} sources The source files that will be
     *     combined.
     * @param {string|File} destination The file you would like the
     *     source files combined into.
     * @param {CombineOptions} [options] Configuration options.
     * @param {CombineCallback} [callback] Callback function.
     * @returns {Promise<CombineResponse>}
     *
     * @example
     * const logBucket = storage.bucket('log-bucket');
     *
     * const sources = [
     *   logBucket.file('2013-logs.txt'),
     *   logBucket.file('2014-logs.txt')
     * ];
     *
     * const allLogs = logBucket.file('all-logs.txt');
     *
     * logBucket.combine(sources, allLogs, function(err, newFile, apiResponse) {
     *   // newFile === allLogs
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * logBucket.combine(sources, allLogs).then(function(data) {
     *   const newFile = data[0];
     *   const apiResponse = data[1];
     * });
     */
    combine(sources, destination, optionsOrCallback, callback) {
        if (!Array.isArray(sources) || 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.');
        }
        let options = {};
        if (typeof optionsOrCallback === 'function') {
            callback = optionsOrCallback;
        }
        else if (optionsOrCallback) {
            options = optionsOrCallback;
        }
        const convertToFile = (file) => {
            if (file instanceof file_1.File) {
                return file;
            }
            return this.file(file);
        };
        // tslint:disable-next-line:no-any
        sources = sources.map(convertToFile);
        const destinationFile = convertToFile(destination);
        callback = callback || common_1.util.noop;
        if (!destinationFile.metadata.contentType) {
            const destinationContentType = mime.contentType(destinationFile.name);
            if (destinationContentType) {
                destinationFile.metadata.contentType = destinationContentType;
            }
        }
        // Make the request from the destination File object.
        destinationFile.request({
            method: 'POST',
            uri: '/compose',
            json: {
                destination: {
                    contentType: destinationFile.metadata.contentType,
                },
                sourceObjects: sources.map(source => {
                    const sourceObject = {
                        name: source.name,
                    };
                    if (source.metadata && source.metadata.generation) {
                        sourceObject.generation = source.metadata.generation;
                    }
                    return sourceObject;
                }),
            },
            qs: options,
        }, (err, resp) => {
            if (err) {
                callback(err, null, resp);
                return;
            }
            callback(null, destinationFile, resp);
        });
    }
    /**
     * See a [Objects:
     * watchAll request
     * body](https://cloud.google.com/storage/docs/json_api/v1/objects/watchAll).
     *
     * @typedef {object} CreateChannelConfig
     * @property {string} address The address where notifications are
     *     delivered for this channel.
     * @extends WatchAllOptions
     */
    /**
     * @typedef {object} CreateChannelOptions
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @typedef {array} CreateChannelResponse
     * @property {Channel} 0 The new {@link Channel}.
     * @property {object} 1 The full API response.
     */
    /**
     * @callback CreateChannelCallback
     * @param {?Error} err Request error, if any.
     * @param {Channel} channel The new {@link Channel}.
     * @param {object} apiResponse The full API response.
     */
    /**
     * Create a channel that will be notified when objects in this bucket changes.
     *
     * @throws {Error} If an ID is not provided.
     * @throws {Error} If an address is not provided.
     *
     * @see [Objects: watchAll API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/watchAll}
     *
     * @param {string} id The ID of the channel to create.
     * @param {CreateChannelConfig} config Configuration for creating channel.
     * @param {CreateChannelOptions} [options] Configuration options.
     * @param {CreateChannelCallback} [callback] Callback function.
     * @returns {Promise<CreateChannelResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     * const id = 'new-channel-id';
     *
     * const config = {
     *   address: 'https://...'
     * };
     *
     * bucket.createChannel(id, config, function(err, channel, apiResponse) {
     *   if (!err) {
     *     // Channel created successfully.
     *   }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.createChannel(id, config).then(function(data) {
     *   const channel = data[0];
     *   const apiResponse = data[1];
     * });
     */
    createChannel(id, config, optionsOrCallback, callback) {
        if (typeof id !== 'string') {
            throw new Error('An ID is required to create a channel.');
        }
        if (typeof config.address !== 'string') {
            throw new Error('An address is required to create a channel.');
        }
        let options = {};
        if (typeof optionsOrCallback === 'function') {
            callback = optionsOrCallback;
        }
        else if (optionsOrCallback) {
            options = optionsOrCallback;
        }
        this.request({
            method: 'POST',
            uri: '/o/watch',
            json: Object.assign({
                id,
                type: 'web_hook',
            }, config),
            qs: options,
        }, (err, apiResponse) => {
            if (err) {
                callback(err, null, apiResponse);
                return;
            }
            const resourceId = apiResponse.resourceId;
            const channel = this.storage.channel(id, resourceId);
            channel.metadata = apiResponse;
            callback(null, channel, apiResponse);
        });
    }
    /**
     * Metadata to set for the Notification.
     *
     * @typedef {object} CreateNotificationOptions
     * @property {object} [customAttributes] An optional list of additional
     *     attributes to attach to each Cloud PubSub message published for this
     *     notification subscription.
     * @property {string[]} [eventTypes] If present, only send notifications about
     *     listed event types. If empty, sent notifications for all event types.
     * @property {string} [objectNamePrefix] If present, only apply this
     *     notification configuration to object names that begin with this prefix.
     * @property {string} [payloadFormat] The desired content of the Payload.
     *     Defaults to `JSON_API_V1`.
     *
     *     Acceptable values are:
     *     - `JSON_API_V1`
     *
     *     - `NONE`
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @callback CreateNotificationCallback
     * @param {?Error} err Request error, if any.
     * @param {Notification} notification The new {@link Notification}.
     * @param {object} apiResponse The full API response.
     */
    /**
     * @typedef {array} CreateNotificationResponse
     * @property {Notification} 0 The new {@link Notification}.
     * @property {object} 1 The full API response.
     */
    /**
     * Creates a notification subscription for the bucket.
     *
     * @see [Notifications: insert]{@link https://cloud.google.com/storage/docs/json_api/v1/notifications/insert}
     *
     * @param {Topic|string} topic The Cloud PubSub topic to which this
     *     subscription publishes. If the project ID is omitted, the current
     * project ID will be used.
     *
     *     Acceptable formats are:
     *     - `projects/grape-spaceship-123/topics/my-topic`
     *
     *     - `my-topic`
     * @param {CreateNotificationOptions} [options] Metadata to set for the
     *     notification.
     * @param {CreateNotificationCallback} [callback] Callback function.
     * @returns {Promise<CreateNotificationResponse>}
     * @throws {Error} If a valid topic is not provided.
     * @see Notification#create
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const myBucket = storage.bucket('my-bucket');
     *
     * const callback = function(err, notification, apiResponse) {
     *   if (!err) {
     *     // The notification was created successfully.
     *   }
     * };
     *
     * myBucket.createNotification('my-topic', callback);
     *
     * //-
     * // Configure the nofiication by providing Notification metadata.
     * //-
     * const metadata = {
     *   objectNamePrefix: 'prefix-'
     * };
     *
     * myBucket.createNotification('my-topic', metadata, callback);
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * myBucket.createNotification('my-topic').then(function(data) {
     *   const notification = data[0];
     *   const apiResponse = data[1];
     * });
     *
     * @example <caption>include:samples/notifications.js</caption>
     * region_tag:storage_create_notification
     * Another example:
     */
    createNotification(topic, optionsOrCallback, callback) {
        let options = {};
        if (typeof optionsOrCallback === 'function') {
            callback = optionsOrCallback;
        }
        else if (optionsOrCallback) {
            options = optionsOrCallback;
        }
        const topicIsObject = topic !== null && typeof topic === 'object';
        if (topicIsObject && common_1.util.isCustomType(topic, 'pubsub/topic')) {
            // tslint:disable-next-line:no-any
            topic = topic.name;
        }
        if (typeof topic !== 'string') {
            throw new Error('A valid topic name is required.');
        }
        const body = Object.assign({ topic }, options);
        if (body.topic.indexOf('projects') !== 0) {
            body.topic = 'projects/{{projectId}}/topics/' + body.topic;
        }
        body.topic = '//pubsub.googleapis.com/' + body.topic;
        if (!body.payloadFormat) {
            body.payloadFormat = 'JSON_API_V1';
        }
        const query = {};
        if (body.userProject) {
            query.userProject = body.userProject;
            delete body.userProject;
        }
        this.request({
            method: 'POST',
            uri: '/notificationConfigs',
            json: snakeize(body),
            qs: query,
        }, (err, apiResponse) => {
            if (err) {
                callback(err, null, apiResponse);
                return;
            }
            const notification = this.notification(apiResponse.id);
            notification.metadata = apiResponse;
            callback(null, notification, apiResponse);
        });
    }
    /**
     * @typedef {object} DeleteFilesOptions Query object. See {@link Bucket#getFiles}
     *     for all of the supported properties.
     * @property {boolean} [force] Suppress errors until all files have been
     *     processed.
     */
    /**
     * @callback DeleteFilesCallback
     * @param {?Error|?Error[]} err Request error, if any, or array of errors from
     *     files that were not able to be deleted.
     * @param {object} [apiResponse] The full API response.
     */
    /**
     * Iterate over the bucket's files, calling `file.delete()` on each.
     *
     * <strong>This is not an atomic request.</strong> A delete attempt will be
     * made for each file individually. Any one can fail, in which case only a
     * portion of the files you intended to be deleted would have.
     *
     * 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 until all files have had a chance
     * to be processed.
     *
     * The `query` object passed as the first argument will also be passed to
     * {@link Bucket#getFiles}.
     *
     * @see [Objects: delete API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/delete}
     *
     * @param {DeleteFilesOptions} [query] Query object. See {@link Bucket#getFiles}
     * @param {DeleteFilesCallback} [callback] Callback function.
     * @returns {Promise}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * //-
     * // Delete all of the files in the bucket.
     * //-
     * bucket.deleteFiles(function(err) {});
     *
     * //-
     * // By default, if a file cannot be deleted, this method will stop deleting
     * // files from your bucket. You can override this setting with `force:
     * // true`.
     * //-
     * bucket.deleteFiles({
     *   force: true
     * }, function(errors) {
     *   // `errors`:
     *   //    Array of errors if any occurred, otherwise null.
     * });
     *
     * //-
     * // The first argument to this method acts as a query to
     * // {@link Bucket#getFiles}. As an example, you can delete files
     * // which match a prefix.
     * //-
     * bucket.deleteFiles({
     *   prefix: 'images/'
     * }, function(err) {
     *   if (!err) {
     *     // All files in the `images` directory have been deleted.
     *   }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.deleteFiles().then(function() {});
     */
    deleteFiles(queryOrCallback, callback) {
        let query = {};
        if (typeof queryOrCallback === 'function') {
            callback = queryOrCallback;
        }
        else if (queryOrCallback) {
            query = queryOrCallback;
        }
        const MAX_PARALLEL_LIMIT = 10;
        const errors = [];
        this.getFiles(query, (err, files) => {
            if (err) {
                callback(err, {});
                return;
            }
            const deleteFile = (file, callback) => {
                file.delete(query, err => {
                    if (err) {
                        if (query.force) {
                            errors.push(err);
                            callback();
                            return;
                        }
                        callback(err);
                        return;
                    }
                    callback(null);
                });
            };
            // Iterate through each file and attempt to delete it.
            async.eachLimit(files, MAX_PARALLEL_LIMIT, deleteFile, err => {
                if (err || errors.length > 0) {
                    callback(err || errors);
                    return;
                }
                callback(null);
            });
        });
    }
    /**
     * @typedef {array} DeleteLabelsResponse
     * @property {object} 0 The full API response.
     */
    /**
     * @callback DeleteLabelsCallback
     * @param {?Error} err Request error, if any.
     * @param {object} metadata Bucket's metadata.
     */
    /**
     * Delete one or more labels from this bucket.
     *
     * @param {string|string[]} labels The labels to delete. If no labels are
     *     provided, all of the labels are removed.
     * @param {DeleteLabelsCallback} [callback] Callback function.
     * @returns {Promise<DeleteLabelsResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * //-
     * // Delete all of the labels from this bucket.
     * //-
     * bucket.deleteLabels(function(err, apiResponse) {});
     *
     * //-
     * // Delete a single label.
     * //-
     * bucket.deleteLabels('labelone', function(err, apiResponse) {});
     *
     * //-
     * // Delete a specific set of labels.
     * //-
     * bucket.deleteLabels([
     *   'labelone',
     *   'labeltwo'
     * ], function(err, apiResponse) {});
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.deleteLabels().then(function(data) {
     *   const apiResponse = data[0];
     * });
     */
    deleteLabels(labelsOrCallback, callback) {
        let labels = new Array();
        if (typeof labelsOrCallback === 'function') {
            callback = labelsOrCallback;
        }
        else if (labelsOrCallback) {
            labels = arrify(labelsOrCallback);
        }
        const deleteLabels = (labels) => {
            const nullLabelMap = labels.reduce((nullLabelMap, labelKey) => {
                nullLabelMap[labelKey] = null;
                return nullLabelMap;
            }, {});
            this.setLabels(nullLabelMap, callback);
        };
        if (labels.length === 0) {
            this.getLabels((err, labels) => {
                if (err) {
                    callback(err);
                    return;
                }
                deleteLabels(Object.keys(labels));
            });
        }
        else {
            deleteLabels(labels);
        }
    }
    /**
     * @typedef {array} DisableRequesterPaysResponse
     * @property {object} 0 The full API response.
     */
    /**
     * @callback DisableRequesterPaysCallback
     * @param {?Error} err Request error, if any.
     * @param {object} apiResponse The full API response.
     */
    /**
     * <div class="notice">
     *   <strong>Early Access Testers Only</strong>
     *   <p>
     *     This feature is not yet widely-available.
     *   </p>
     * </div>
     *
     * Disable `requesterPays` functionality from this bucket.
     *
     * @param {DisableRequesterPaysCallback} [callback] Callback function.
     * @returns {Promise<DisableRequesterPaysCallback>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * bucket.disableRequesterPays(function(err, apiResponse) {
     *   if (!err) {
     *     // requesterPays functionality disabled successfully.
     *   }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.disableRequesterPays().then(function(data) {
     *   const apiResponse = data[0];
     * });
     *
     * @example <caption>include:samples/requesterPays.js</caption>
     * region_tag:storage_disable_requester_pays
     * Example of disabling requester pays:
     */
    disableRequesterPays(callback) {
        this.setMetadata({
            billing: {
                requesterPays: false,
            },
        }, callback || common_1.util.noop);
    }
    /**
     * @typedef {array} EnableRequesterPaysResponse
     * @property {object} 0 The full API response.
     */
    /**
     * @callback EnableRequesterPaysCallback
     * @param {?Error} err Request error, if any.
     * @param {object} apiResponse The full API response.
     */
    /**
     * <div class="notice">
     *   <strong>Early Access Testers Only</strong>
     *   <p>
     *     This feature is not yet widely-available.
     *   </p>
     * </div>
     *
     * Enable `requesterPays` functionality for this bucket. This enables you, the
     * bucket owner, to have the requesting user assume the charges for the access
     * to your bucket and its contents.
     *
     * @param {EnableRequesterPaysCallback} [callback] Callback function.
     * @returns {Promise<EnableRequesterPaysResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * bucket.enableRequesterPays(function(err, apiResponse) {
     *   if (!err) {
     *     // requesterPays functionality enabled successfully.
     *   }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.enableRequesterPays().then(function(data) {
     *   const apiResponse = data[0];
     * });
     *
     * @example <caption>include:samples/requesterPays.js</caption>
     * region_tag:storage_enable_requester_pays
     * Example of enabling requester pays:
     */
    enableRequesterPays(callback) {
        this.setMetadata({
            billing: {
                requesterPays: true,
            },
        }, callback || common_1.util.noop);
    }
    /**
     * Create a {@link File} object. See {@link File} to see how to handle
     * the different use cases you may have.
     *
     * @param {string} name The name of the file in this bucket.
     * @param {object} [options] Configuration options.
     * @param {string|number} [options.generation] Only use a specific revision of
     *     this file.
     * @param {string} [options.encryptionKey] A custom encryption key. See
     *     [Customer-supplied Encryption
     * Keys](https://cloud.google.com/storage/docs/encryption#customer-supplied).
     * @param {string} [options.kmsKeyName] The name of the Cloud KMS key that will
     *     be used to encrypt the object. Must be in the format:
     *     `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`.
     *     KMS key ring must use the same location as the bucket.
     * @returns {File}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     * const file = bucket.file('my-existing-file.png');
     */
    file(name, options) {
        if (!name) {
            throw Error('A file name must be specified.');
        }
        return new file_1.File(this, name, options);
    }
    /**
     * @typedef {array} GetFilesResponse
     * @property {File[]} 0 Array of {@link File} instances.
     */
    /**
     * @callback GetFilesCallback
     * @param {?Error} err Request error, if any.
     * @param {File[]} files Array of {@link File} instances.
     */
    /**
     * Query object for listing files.
     *
     * @typedef {object} GetFilesOptions
     * @property {boolean} [autoPaginate=true] Have pagination handled
     *     automatically.
     * @property {string} [delimiter] Results will contain only objects whose
     *     names, aside from the prefix, do not contain delimiter. Objects whose
     *     names, aside from the prefix, contain delimiter will have their name
     *     truncated after the delimiter, returned in `apiResponse.prefixes`.
     *     Duplicate prefixes are omitted.
     * @property {string} [directory] Filter results based on a directory name, or
     *     more technically, a "prefix".
     * @property {string} [prefix] Filter results to objects whose names begin
     *     with this prefix.
     * @property {number} [maxApiCalls] Maximum number of API calls to make.
     * @property {number} [maxResults] Maximum number of items plus prefixes to
     *     return.
     * @property {string} [pageToken] A previously-returned page token
     *     representing part of the larger set of results to view.
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     * @property {boolean} [versions] If true, returns File objects scoped to
     *     their versions.
     */
    /**
     * Get {@link File} objects for the files currently in the bucket.
     *
     * @see [Objects: list API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/list}
     *
     * @param {GetFilesOptions} [query] Query object for listing files.
     * @param {GetFilesCallback} [callback] Callback function.
     * @returns {Promise<GetFilesResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * bucket.getFiles(function(err, files) {
     *   if (!err) {
     *     // files is an array of File objects.
     *   }
     * });
     *
     * //-
     * // If your bucket has versioning enabled, you can get all of your files
     * // scoped to their generation.
     * //-
     * bucket.getFiles({
     *   versions: true
     * }, function(err, files) {
     *   // Each file is scoped to its generation.
     * });
     *
     * //-
     * // To control how many API requests are made and page through the results
     * // manually, set `autoPaginate` to `false`.
     * //-
     * const callback = function(err, files, nextQuery, apiResponse) {
     *   if (nextQuery) {
     *     // More results exist.
     *     bucket.getFiles(nextQuery, callback);
     *   }
     *
     *   // The `metadata` property is populated for you with the metadata at the
     *   // time of fetching.
     *   files[0].metadata;
     *
     *   // However, in cases where you are concerned the metadata could have
     *   // changed, use the `getMetadata` method.
     *   files[0].getMetadata(function(err, metadata) {});
     * };
     *
     * bucket.getFiles({
     *   autoPaginate: false
     * }, callback);
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.getFiles().then(function(data) {
     *   const files = data[0];
     * });
     *
     * @example <caption>include:samples/files.js</caption>
     * region_tag:storage_list_files
     * Another example:
     *
     * @example <caption>include:samples/files.js</caption>
     * region_tag:storage_list_files_with_prefix
     * Example of listing files, filtered by a prefix:
     */
    getFiles(queryOrCallback, callback) {
        let query = typeof queryOrCallback === 'object' ? queryOrCallback : {};
        if (!callback) {
            callback = queryOrCallback;
        }
        query = Object.assign({}, query);
        if (query.directory) {
            query.prefix = `${query.directory}/`.replace(/\/*$/, '/');
            delete query.directory;
        }
        this.request({
            uri: '/o',
            qs: query,
        }, (err, resp) => {
            if (err) {
                // tslint:disable-next-line:no-any
                callback(err, null, null, resp);
                return;
            }
            const files = arrify(resp.items).map((file) => {
                const options = {};
                if (query.versions) {
                    options.generation = file.generation;
                }
                if (file.kmsKeyName) {
                    options.kmsKeyName = file.kmsKeyName;
                }
                const fileInstance = this.file(file.name, options);
                fileInstance.metadata = file;
                return fileInstance;
            });
            let nextQuery = null;
            if (resp.nextPageToken) {
                nextQuery = Object.assign({}, query, {
                    pageToken: resp.nextPageToken,
                });
            }
            // tslint:disable-next-line:no-any
            callback(null, files, nextQuery, resp);
        });
    }
    /**
     * @typedef {object} GetLabelsOptions Configuration options for Bucket#getLabels().
     * @param {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @typedef {array} GetLabelsResponse
     * @property {object} 0 Object of labels currently set on this bucket.
     */
    /**
     * @callback GetLabelsCallback
     * @param {?Error} err Request error, if any.
     * @param {object} labels Object of labels currently set on this bucket.
     */
    /**
     * Get the labels currently set on this bucket.
     *
     * @param {object} [options] Configuration options.
     * @param {string} [options.userProject] The ID of the project which will be
     *     billed for the request.
     * @param {GetLabelsCallback} [callback] Callback function.
     * @returns {Promise<GetLabelsCallback>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * bucket.getLabels(function(err, labels) {
     *   if (err) {
     *     // Error handling omitted.
     *   }
     *
     *   // labels = {
     *   //   label: 'labelValue',
     *   //   ...
     *   // }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.getLabels().then(function(data) {
     *   const labels = data[0];
     * });
     */
    getLabels(optionsOrCallback, callback) {
        let options = {};
        if (typeof optionsOrCallback === 'function') {
            callback = optionsOrCallback;
        }
        else if (optionsOrCallback) {
            options = optionsOrCallback;
        }
        this.getMetadata(options, (err, metadata) => {
            if (err) {
                callback(err, null);
                return;
            }
            callback(null, metadata.labels || {});
        });
    }
    /**
     * @typedef {object} GetNotificationOptions Configuration options for Bucket#getNotification().
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @callback GetNotificationsCallback
     * @param {?Error} err Request error, if any.
     * @param {Notification[]} notifications Array of {@link Notification}
     *     instances.
     * @param {object} apiResponse The full API response.
     */
    /**
     * @typedef {array} GetNotificationsResponse
     * @property {Notification[]} 0 Array of {@link Notification} instances.
     * @property {object} 1 The full API response.
     */
    /**
     * Retrieves a list of notification subscriptions for a given bucket.
     *
     * @see [Notifications: list]{@link https://cloud.google.com/storage/docs/json_api/v1/notifications/list}
     *
     * @param {GetNotificationsOptions} [options] Configuration options.
     * @param {GetNotificationsCallback} [callback] Callback function.
     * @returns {Promise<GetNotificationsResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('my-bucket');
     *
     * bucket.getNotifications(function(err, notifications, apiResponse) {
     *   if (!err) {
     *     // notifications is an array of Notification objects.
     *   }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.getNotifications().then(function(data) {
     *   const notifications = data[0];
     *   const apiResponse = data[1];
     * });
     *
     * @example <caption>include:samples/notifications.js</caption>
     * region_tag:storage_list_notifications
     * Another example:
     */
    getNotifications(optionsOrCallback, callback) {
        let options = {};
        if (typeof optionsOrCallback === 'function') {
            callback = optionsOrCallback;
        }
        else if (optionsOrCallback) {
            options = optionsOrCallback;
        }
        this.request({
            uri: '/notificationConfigs',
            qs: options,
        }, (err, resp) => {
            if (err) {
                callback(err, null, resp);
                return;
            }
            const notifications = arrify(resp.items).map((notification) => {
                const notificationInstance = this.notification(notification.id);
                notificationInstance.metadata = notification;
                return notificationInstance;
            });
            callback(null, notifications, resp);
        });
    }
    /**
     * @callback BucketLockCallback
     * @param {?Error} err Request error, if any.
     * @param {object} apiResponse The full API response.
     */
    /**
     * Lock a previously-defined retention policy. This will prevent changes to
     * the policy.
     *
     * @throws {Error} if a metageneration is not provided.
     *
     * @param {Number|String} metageneration The bucket's metageneration. This is
     *     accesssible from calling {@link File#getMetadata}.
     * @param {BucketLockCallback} [callback] Callback function.
     * @returns {Promise<BucketLockResponse>}
     *
     * @example
     * const storage = require('@google-cloud/storage')();
     * const bucket = storage.bucket('albums');
     *
     * const metageneration = 2;
     *
     * bucket.lock(metageneration, function(err, apiResponse) {});
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.lock(metageneration).then(function(data) {
     *   const apiResponse = data[0];
     * });
     */
    lock(metageneration, callback) {
        const metatype = typeof metageneration;
        if (metatype !== 'number' && metatype !== 'string') {
            throw new Error('A metageneration must be provided.');
        }
        this.request({
            method: 'POST',
            uri: '/lockRetentionPolicy',
            qs: {
                ifMetagenerationMatch: metageneration,
            },
        }, callback);
    }
    /**
     * @typedef {array} MakeBucketPrivateResponse
     * @property {File[]} 0 List of files made private.
     */
    /**
     * @callback MakeBucketPrivateCallback
     * @param {?Error} err Request error, if any.
     * @param {File[]} files List of files made private.
     */
    /**
     * @typedef {object} MakeBucketPrivateOptions
     * @param {boolean} [includeFiles=false] Make each file in the bucket
     *     private.
     * @param {boolean} [force] Queue errors occurred while making files
     *     private until all files have been processed.
     * @param {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * 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
     * {@link 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 processed, 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.
     *
     * @see [Buckets: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch}
     *
     * @param {MakeBucketPrivateOptions} [options] Configuration options.
     * @param {MakeBucketPrivateCallback} [callback] Callback function.
     * @returns {Promise<MakeBucketPrivateResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * //-
     * // Make the bucket private.
     * //-
     * bucket.makePrivate(function(err) {});
     *
     * //-
     * // Make the bucket and its contents private.
     * //-
     * const 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.
     * //-
     * const 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.
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.makePrivate(opts).then(function(data) {
     *   const files = data[0];
     * });
     */
    makePrivate(optionsOrCallback, callback) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        callback =
            typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
        options.private = true;
        const setPredefinedAcl = (done) => {
            const query = {
                predefinedAcl: 'projectPrivate',
            };
            if (options.userProject) {
                query.userProject = options.userProject;
            }
            this.setMetadata({
                // You aren't allowed to set both predefinedAcl & acl properties on
                // a bucket so acl must explicitly be nullified.
                acl: null,
            }, query, done);
        };
        const makeFilesPrivate = (done) => {
            if (!options.includeFiles) {
                done();
                return;
            }
            this.makeAllFilesPublicPrivate_(options, done);
        };
        // tslint:disable-next-line no-any
        async.series([setPredefinedAcl, makeFilesPrivate], callback);
    }
    /**
     * @typedef {object} MakeBucketPublicOptions
     * @param {boolean} [includeFiles=false] Make each file in the bucket
     *     private.
     * @param {boolean} [force] Queue errors occurred while making files
     *     private until all files have been processed.
     */
    /**
     * @callback MakeBucketPublicCallback
     * @param {?Error} err Request error, if any.
     * @param {File[]} files List of files made public.
     */
    /**
     * @typedef {array} MakeBucketPublicResponse
     * @property {File[]} 0 List of files made public.
     */
    /**
     * 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
     * {@link 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 processed, 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.
     *
     * @see [Buckets: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch}
     *
     * @param {MakeBucketPublicOptions} [options] Configuration options.
     * @param {MakeBucketPublicCallback} [callback] Callback function.
     * @returns {Promise<MakeBucketPublicResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * //-
     * // Make the bucket publicly readable.
     * //-
     * bucket.makePublic(function(err) {});
     *
     * //-
     * // Make the bucket and its contents publicly readable.
     * //-
     * const 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.
     * //-
     * const 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.
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.makePublic(opts).then(function(data) {
     *   const files = data[0];
     * });
     */
    makePublic(optionsOrCallback, callback) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        callback =
            typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
        const req = extend(true, { public: true }, options);
        const addAclPermissions = (done) => {
            // Allow reading bucket contents while preserving original permissions.
            this.acl.add({
                entity: 'allUsers',
                role: 'READER',
            }, done);
        };
        const addDefaultAclPermissions = (done) => {
            this.acl.default.add({
                entity: 'allUsers',
                role: 'READER',
            }, done);
        };
        const makeFilesPublic = (done) => {
            if (!req.includeFiles) {
                done();
                return;
            }
            this.makeAllFilesPublicPrivate_(req, done);
        };
        // tslint:disable-next-line:no-any
        async.series([addAclPermissions, addDefaultAclPermissions, makeFilesPublic], callback);
    }
    /**
     * Get a reference to a Cloud Pub/Sub Notification.
     *
     * @param {string} id ID of notification.
     * @returns {Notification}
     * @see Notification
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('my-bucket');
     * const notification = bucket.notification('1');
     */
    notification(id) {
        if (!id) {
            throw new Error('You must supply a notification ID.');
        }
        return new notification_1.Notification(this, id);
    }
    /**
     * Remove an already-existing retention policy from this bucket, if it is not
     * locked.
     *
     * @param {SetBucketMetadataCallback} [callback] Callback function.
     * @returns {Promise<SetBucketMetadataResponse>}
     *
     * @example
     * const storage = require('@google-cloud/storage')();
     * const bucket = storage.bucket('albums');
     *
     * bucket.removeRetentionPeriod(function(err, apiResponse) {});
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.removeRetentionPeriod().then(function(data) {
     *   const apiResponse = data[0];
     * });
     */
    removeRetentionPeriod(callback) {
        this.setMetadata({
            retentionPolicy: null,
        }, callback);
    }
    /**
     * Makes request and applies userProject query parameter if necessary.
     *
     * @private
     *
     * @param {object} reqOpts - The request options.
     * @param {function} callback - The callback function.
     */
    request(reqOpts, callback) {
        if (this.userProject && (!reqOpts.qs || !reqOpts.qs.userProject)) {
            reqOpts.qs = extend(reqOpts.qs, { userProject: this.userProject });
        }
        return super.request(reqOpts, callback);
    }
    /**
     * @typedef {array} SetLabelsResponse
     * @property {object} 0 The bucket metadata.
     */
    /**
     * @callback SetLabelsCallback
     * @param {?Error} err Request error, if any.
     * @param {object} metadata The bucket metadata.
     */
    /**
     * @typedef {object} SetLabelsOptions Configuration options for Bucket#setLabels().
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * Set labels on the bucket.
     *
     * This makes an underlying call to {@link Bucket#setMetadata}, which
     * is a PATCH request. This means an individual label can be overwritten, but
     * unmentioned labels will not be touched.
     *
     * @param {object<string, string>} labels Labels to set on the bucket.
     * @param {object} [options] Configuration options.
     * @param {SetLabelsCallback} [callback] Callback function.
     * @returns {Promise<SetLabelsResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * const labels = {
     *   labelone: 'labelonevalue',
     *   labeltwo: 'labeltwovalue'
     * };
     *
     * bucket.setLabels(labels, function(err, metadata) {
     *   if (!err) {
     *     // Labels set successfully.
     *   }
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.setLabels(labels).then(function(data) {
     *   const metadata = data[0];
     * });
     */
    setLabels(labels, optionsOrCallback, callback) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        callback =
            typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
        callback = callback || common_1.util.noop;
        this.setMetadata({ labels }, options, callback);
    }
    /**
     * Lock all objects contained in the bucket, based on their creation time. Any
     * attempt to overwrite or delete objects younger than the retention period
     * will result in a `PERMISSION_DENIED` error.
     *
     * An unlocked retention policy can be modified or removed from the bucket via
     * {@link File#removeRetentionPeriod} and {@link File#setRetentionPeriod}. A
     * locked retention policy cannot be removed or shortened in duration for the
     * lifetime of the bucket. Attempting to remove or decrease period of a locked
     * retention policy will result in a `PERMISSION_DENIED` error. You can still
     * increase the policy.
     *
     * @param {*} duration In seconds, the minimum retention time for all objects
     *     contained in this bucket.
     * @param {SetBucketMetadataCallback} [callback] Callback function.
     * @returns {Promise<SetBucketMetadataResponse>}
     *
     * @example
     * const storage = require('@google-cloud/storage')();
     * const bucket = storage.bucket('albums');
     *
     * const DURATION_SECONDS = 15780000; // 6 months.
     *
     * //-
     * // Lock the objects in this bucket for 6 months.
     * //-
     * bucket.setRetentionPeriod(DURATION_SECONDS, function(err, apiResponse) {});
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.setRetentionPeriod(DURATION_SECONDS).then(function(data) {
     *   const apiResponse = data[0];
     * });
     */
    setRetentionPeriod(duration, callback) {
        this.setMetadata({
            retentionPolicy: {
                retentionPeriod: duration,
            },
        }, callback);
    }
    /**
     * @typedef {object} SetBucketStorageClassOptions
     * @param {string} [userProject] - The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @callback SetBucketStorageClassCallback
     * @param {?Error} err Request error, if any.
     */
    /**
     * Set the default storage class for new files in this bucket.
     *
     * @see [Storage Classes]{@link https://cloud.google.com/storage/docs/storage-classes}
     *
     * @param {string} storageClass The new storage class. (`multi_regional`,
     *     `regional`, `standard`, `nearline`, `coldline`, or
     *     `durable_reduced_availability`)
     * @param {object} [options] Configuration options.
     * @param {string} [options.userProject] - The ID of the project which will be
     *     billed for the request.
     * @param {SetStorageClassCallback} [callback] Callback function.
     * @returns {Promise}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * bucket.setStorageClass('regional', function(err, apiResponse) {
     *   if (err) {
     *     // Error handling omitted.
     *   }
     *
     *   // The storage class was updated successfully.
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.setStorageClass('regional').then(function() {});
     */
    setStorageClass(storageClass, optionsOrCallback, callback) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        callback =
            typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
        // In case we get input like `storageClass`, convert to `storage_class`.
        storageClass = storageClass
            .replace(/-/g, '_')
            .replace(/([a-z])([A-Z])/g, (_, low, up) => {
            return low + '_' + up;
        })
            .toUpperCase();
        this.setMetadata({ storageClass }, options, callback);
    }
    /**
     * Set a user project to be billed for all requests made from this Bucket
     * object and any files referenced from this Bucket object.
     *
     * @param {string} userProject The user project.
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * bucket.setUserProject('grape-spaceship-123');
     */
    setUserProject(userProject) {
        this.userProject = userProject;
        const methods = [
            'create',
            'delete',
            'exists',
            'get',
            'getMetadata',
            'setMetadata',
        ];
        methods.forEach(method => {
            const methodConfig = this.methods[method];
            if (typeof methodConfig === 'object') {
                if (typeof methodConfig.reqOpts === 'object') {
                    extend(methodConfig.reqOpts.qs, { userProject });
                }
                else {
                    methodConfig.reqOpts = {
                        qs: { userProject },
                    };
                }
            }
        });
    }
    /**
     * @typedef {object} UploadOptions Configuration options for Bucket#upload().
     * @param {string|File} [options.destination] The place to save
     *     your file. If given a string, the file will be uploaded to the bucket
     *     using the string as a filename. When given a File object, your local
     * file will be uploaded to the File object's bucket and under the File
     * object's name. Lastly, when this argument is omitted, the file is uploaded
     * to your bucket using the name of the local file.
     * @param {string} [options.encryptionKey] A custom encryption key. See
     *     [Customer-supplied Encryption
     * Keys](https://cloud.google.com/storage/docs/encryption#customer-supplied).
     * @param {boolean} [options.gzip] Automatically gzip the file. This will set
     *     `options.metadata.contentEncoding` to `gzip`.
     * @param {string} [options.kmsKeyName] The name of the Cloud KMS key that will
     *     be used to encrypt the object. Must be in the format:
     *     `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`.
     * @param {object} [options.metadata] See an
     *     [Objects: insert request
     * body](https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request_properties_JSON).
     * @param {string} [options.offset] The starting byte of the upload stream, for
     *     resuming an interrupted upload. Defaults to 0.
     * @param {string} [options.predefinedAcl] Apply a predefined set of access
     *     controls to this object.
     *
     *     Acceptable values are:
     *     - **`authenticatedRead`** - Object owner gets `OWNER` access, and
     *       `allAuthenticatedUsers` get `READER` access.
     *
     *     - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
     *       project team owners get `OWNER` access.
     *
     *     - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
     *       team owners get `READER` access.
     *
     *     - **`private`** - Object owner gets `OWNER` access.
     *
     *     - **`projectPrivate`** - Object owner gets `OWNER` access, and project
     *       team members get access according to their roles.
     *
     *     - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
     * get `READER` access.
     * @param {boolean} [options.private] Make the uploaded file private. (Alias for
     *     `options.predefinedAcl = 'private'`)
     * @param {boolean} [options.public] Make the uploaded file public. (Alias for
     *     `options.predefinedAcl = 'publicRead'`)
     * @param {boolean} [options.resumable] Force a resumable upload. (default:
     *     true for files larger than 5 MB).
     * @param {string} [options.uri] The URI for an already-created resumable
     *     upload. See {@link File#createResumableUpload}.
     * @param {string} [options.userProject] The ID of the project which will be
     *     billed for the request.
     * @param {string|boolean} [options.validation] Possible values: `"md5"`,
     *     `"crc32c"`, or `false`. By default, data integrity is validated with an
     *     MD5 checksum for maximum reliability. CRC32c will provide better
     *     performance with less reliability. You may also choose to skip
     * validation completely, however this is **not recommended**.
     */
    /**
     * @typedef {array} UploadResponse
     * @property {object} 0 The uploaded {@link File}.
     * @property {object} 1 The full API response.
     */
    /**
     * @callback UploadCallback
     * @param {?Error} err Request error, if any.
     * @param {object} file The uploaded {@link File}.
     * @param {object} apiResponse The full API response.
     */
    /**
     * Upload a file to the bucket. This is a convenience method that wraps
     * {@link File#createWriteStream}.
     *
     * You can specify whether or not an upload is resumable by setting
     * `options.resumable`. *Resumable uploads are enabled by default if your
     * input file is larger than 5 MB.*
     *
     * For faster crc32c computation, you must manually install
     * [`fast-crc32c`](http://www.gitnpm.com/fast-crc32c):
     *
     *     $ npm install --save fast-crc32c
     *
     * @see [Upload Options (Simple or Resumable)]{@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload#uploads}
     * @see [Objects: insert API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert}
     *
     * @param {string} pathString The fully qualified path to the file you
     *     wish to upload to your bucket.
     * @param {UploadOptions} [options] Configuration options.
     * @param {UploadCallback} [callback] Callback function.
     * @returns {Promise<UploadResponse>}
     *
     * @example
     * const {Storage} = require('@google-cloud/storage');
     * const storage = new Storage();
     * const bucket = storage.bucket('albums');
     *
     * //-
     * // Upload a file from a local path.
     * //-
     * bucket.upload('/local/path/image.png', function(err, file, apiResponse) {
     *   // Your bucket now contains:
     *   // - "image.png" (with the contents of `/local/path/image.png')
     *
     *   // `file` is an instance of a File object that refers to your new file.
     * });
     *
     *
     * //-
     * // It's not always that easy. You will likely want to specify the filename
     * // used when your new file lands in your bucket.
     * //
     * // You may also want to set metadata or customize other options.
     * //-
     * const options = {
     *   destination: 'new-image.png',
     *   resumable: true,
     *   validation: 'crc32c',
     *   metadata: {
     *     metadata: {
     *       event: 'Fall trip to the zoo'
     *     }
     *   }
     * };
     *
     * bucket.upload('local-image.png', options, function(err, file) {
     *   // Your bucket now contains:
     *   // - "new-image.png" (with the contents of `local-image.png')
     *
     *   // `file` is an instance of a File object that refers to your new file.
     * });
     *
     * //-
     * // You can also have a file gzip'd on the fly.
     * //-
     * bucket.upload('index.html', { gzip: true }, function(err, file) {
     *   // Your bucket now contains:
     *   // - "index.html" (automatically compressed with gzip)
     *
     *   // Downloading the file with `file.download` will automatically decode
     * the
     *   // file.
     * });
     *
     * //-
     * // You may also re-use a File object, {File}, that references
     * // the file you wish to create or overwrite.
     * //-
     * const options = {
     *   destination: bucket.file('existing-file.png'),
     *   resumable: false
     * };
     *
     * bucket.upload('local-img.png', options, function(err, newFile) {
     *   // Your bucket now contains:
     *   // - "existing-file.png" (with the contents of `local-img.png')
     *
     *   // Note:
     *   // The `newFile` parameter is equal to `file`.
     * });
     *
     * //-
     * // To use
     * // <a
     * href="https://cloud.google.com/storage/docs/encryption#customer-supplied">
     * // Customer-supplied Encryption Keys</a>, provide the `encryptionKey`
     * option.
     * //-
     * const crypto = require('crypto');
     * const encryptionKey = crypto.randomBytes(32);
     *
     * bucket.upload('img.png', {
     *   encryptionKey: encryptionKey
     * }, function(err, newFile) {
     *   // `img.png` was uploaded with your custom encryption key.
     *
     *   // `newFile` is already configured to use the encryption key when making
     *   // operations on the remote object.
     *
     *   // However, to use your encryption key later, you must create a `File`
     *   // instance with the `key` supplied:
     *   const file = bucket.file('img.png', {
     *     encryptionKey: encryptionKey
     *   });
     *
     *   // Or with `file#setEncryptionKey`:
     *   const file = bucket.file('img.png');
     *   file.setEncryptionKey(encryptionKey);
     * });
     *
     * //-
     * // If the callback is omitted, we'll return a Promise.
     * //-
     * bucket.upload('local-image.png').then(function(data) {
     *   const file = data[0];
     * });
     *
     * To upload a file from a URL, use {@link File#createWriteStream}.
     *
     * @example <caption>include:samples/files.js</caption>
     * region_tag:storage_upload_file
     * Another example:
     *
     * @example <caption>include:samples/encryption.js</caption>
     * region_tag:storage_upload_encrypted_file
     * Example of uploading an encrypted file:
     */
    upload(pathString, optionsOrCallback, callback) {
        // tslint:disable-next-line:no-any
        if (global['GCLOUD_SANDBOX_ENV']) {
            return;
        }
        let options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        callback =
            typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
        options = Object.assign({
            metadata: {},
        }, options);
        let newFile;
        if (options.destination instanceof file_1.File) {
            newFile = options.destination;
        }
        else if (options.destination != null &&
            typeof options.destination === 'string') {
            // Use the string as the name of the file.
            newFile = this.file(options.destination, {
                encryptionKey: options.encryptionKey,
                kmsKeyName: options.kmsKeyName,
            });
        }
        else {
            // Resort to using the name of the incoming file.
            const destination = path.basename(pathString);
            newFile = this.file(destination, {
                encryptionKey: options.encryptionKey,
                kmsKeyName: options.kmsKeyName,
            });
        }
        const contentType = mime.contentType(path.basename(pathString));
        if (contentType && !options.metadata.contentType) {
            options.metadata.contentType = contentType;
        }
        if (options.resumable != null && typeof options.resumable === 'boolean') {
            upload();
        }
        else {
            // Determine if the upload should be resumable if it's over the threshold.
            fs.stat(pathString, (err, fd) => {
                if (err) {
                    callback(err);
                    return;
                }
                options.resumable = fd.size > RESUMABLE_THRESHOLD;
                upload();
            });
        }
        function upload() {
            fs.createReadStream(pathString)
                .on('error', callback)
                .pipe(newFile.createWriteStream(options))
                .on('error', callback)
                .on('finish', () => {
                callback(null, newFile, newFile.metadata);
            });
        }
    }
    /**
     * @private
     *
     * @typedef {object} MakeAllFilesPublicPrivateOptions
     * @property {boolean} [force] Suppress errors until all files have been
     *     processed.
     * @property {boolean} [private] Make files private.
     * @property {boolean} [public] Make files public.
     * @property {string} [userProject] The ID of the project which will be
     *     billed for the request.
     */
    /**
     * @private
     *
     * @callback SetBucketMetadataCallback
     * @param {?Error} err Request error, if any.
     * @param {File[]} files Files that were updated.
     */
    /**
     * @typedef {array} MakeAllFilesPublicPrivateResponse
     * @property {File[]} 0 List of files affected.
     */
    /**
     * 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 {MakeAllFilesPublicPrivateOptions} [options] Configuration options.
     * @param {MakeAllFilesPublicPrivateCallback} callback Callback function.
     *
     * @return {Promise<MakeAllFilesPublicPrivateResponse>}
     */
    makeAllFilesPublicPrivate_(optionsOrCallback, callback) {
        const MAX_PARALLEL_LIMIT = 10;
        const errors = [];
        const updatedFiles = [];
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        callback =
            typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
        this.getFiles(options, (err, files) => {
            if (err) {
                callback(err);
                return;
            }
            const processFile = (file, callback) => {
                const processedCallback = (err) => {
                    if (err) {
                        if (options.force) {
                            errors.push(err);
                            callback();
                            return;
                        }
                        callback(err);
                        return;
                    }
                    updatedFiles.push(file);
                    callback();
                };
                if (options.public) {
                    file.makePublic(processedCallback);
                }
                else if (options.private) {
                    file.makePrivate(options, processedCallback);
                }
            };
            // Iterate through each file and make it public or private.
            async.eachLimit(files, MAX_PARALLEL_LIMIT, processFile, (err) => {
                if (err || errors.length > 0) {
                    callback(err || errors, updatedFiles);
                    return;
                }
                callback(null, updatedFiles);
            });
        });
    }
    getId() {
        return this.id;
    }
}
exports.Bucket = Bucket;
/*! Developer Documentation
 *
 * These methods can be auto-paginated.
 */
paginator_1.paginator.extend(Bucket, 'getFiles');
/*! Developer Documentation
 *
 * All async methods (except for streams) will return a Promise in the event
 * that a callback is omitted.
 */
promisify_1.promisifyAll(Bucket, {
    exclude: ['request', 'file', 'notification'],
});
//# sourceMappingURL=bucket.js.map