Containers.js

const _ = require("../tools/lodash");
const request = require('../tools/request');

const ContainersMeta = require("./ContainersMeta");

/**
 *  Create new container, manage metas, get related information, delete, ...
 *
 *  __Available methods :__ *create()*, *create_with_result()*, *delete_objects()*, *delete_objects_with_result()*, *delete()*, *delete_with_result()*, *list()*, *exist()*, *info()*, *metas()*
 */
class Containers {
	/**
	 * Container constructor
	 *
	 * @param {OVHStorage} context OVHObjectStorage context
	 */
	constructor(context) {
		this.context = context;
	}

	/**
	 * Delete container
	 *
	 * @param {String} container Name of container
	 *
	 * @async
	 * @return {Promise<Object>}
	 *
	 * @ignore
	 * @private
	 */
	_delete(container) {
		return new Promise((resolve, reject) => {
			try {
				// check
				if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is expected.");
				if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is not a string.");
				if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter contains special chars.");

				// reformat
				container = _.toSlug(container);

				// call
				request({
					method: 'DELETE',
					uri: encodeURI(this.context.endpoint.url + '/' + container),
					headers: {
						"X-Auth-Token": this.context.token,
						"Accept": "application/json"
					}
				}, (err, res, body) => {
					err = err || request.checkIfResponseIsError(res);
					if (err) // noinspection ExceptionCaughtLocallyJS
						throw new Error(err);

					return resolve(res.headers);
				});
			} catch (e) {
				return reject(e);
			}
		});
	}

	/**
	 * Names object of index, css and error files.
	 * **!! USE ONLY FOR STATIC CONTAINER DECLARATION !!**
	 *
	 * When object is not specified default files are :
	 *  - index : 'index.html'
	 *  - css : 'listing.css'
	 *  - error : 'error.html'
	 *
	 * @typedef {Object} OVHStorageContainerStaticWebContentPages
	 *
	 * @param {String} index This is name of index file used when url of static container is request.
	 * @param {String} css This is name of CSS file used to style index and error file.
	 * @param {String} error This is name of error page used when request status is not 200 or 20x.
	 */

	/**
	 * Create a new container
	 *
	 * @param {String} container Name of container
	 * @param {("public"|"private"|"static")} types Type of container : public, private or static
	 * @param {OVHStorageContainerStaticWebContentPages=} web_content_pages Web page parameters (index file, error file, listing cascading style sheet file) if container is on static type
	 *
	 * @async
	 * @return {Promise<Object>}
	 */
	create(container, types, web_content_pages = {}) {
		return new Promise((resolve, reject) => {
			try {
				// check
				if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is expected.");
				if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is not a string.");
				if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter contains special chars.");

				// reformat
				container = _.toSlug(container);

				let headers = {};
				if (!_.isUndefined(types)) {
					switch (types) {
						case 'public':
							headers['x-container-read'] = '.r:*,.rlistings';
							break;
						case 'private':
							break;
						case 'static':
							headers["x-container-meta-web-listings"] = "true";
							headers["x-container-read"] = ",.r:*,.rlistings";

							if (_.isUndefined(web_content_pages))
								web_content_pages = {};

							headers
								["x-container-meta-web-error"] = (!_.isUndefined(web_content_pages['error']) ? web_content_pages['error'] : "error.html");
							headers
								["x-container-meta-web-listings-css"] = (!_.isUndefined(web_content_pages['css']) ? web_content_pages['css'] : "listing.css");
							headers
								["x-container-meta-web-index"] = (!_.isUndefined(web_content_pages['index']) ? web_content_pages['index'] : "index.html");
							break;
					}
				}

				// call
				request({
					method: 'PUT',
					uri: encodeURI(this.context.endpoint.url + '/' + container),
					headers: Object.assign({
						"X-Auth-Token": this.context.token,
						"Accept": "application/json"
					}, headers)
				}, (err, res, body) => {
					err = err || request.checkIfResponseIsError(res);
					if (err) // noinspection ExceptionCaughtLocallyJS
						throw new Error(err);

					return resolve(res.headers);
				});
			} catch
				(e) {
				return reject(e);
			}
		});
	}

	/**
	 * Create a new container and return boolean as result
	 *
	 * @param {String} container Name of container
	 * @param {("public"|"private"|"static")} types Type of container : public, private or static
	 * @param {OVHStorageContainerStaticWebContentPages=} web_content_pages Web page parameters (index file, error file, listing cascading style sheet file) if container is on static type
	 *
	 * @async
	 * @return {Promise<Boolean>}
	 */
	async create_with_result(container, types, web_content_pages = {}) {
		try {
			await this.context.containers().create(container, types, web_content_pages);
			return true;
		} catch (e) {
			return false;
		}
	}

	/**
	 * Delete all objects in container
	 *
	 * @param {String} container Name of container
	 *
	 * @async
	 * @return {Promise<Object>}
	 */
	delete_objects(container) {
		return new Promise(async (resolve, reject) => {
			try {
				// check
				if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is expected.");
				if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is not a string.");
				if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter contains special chars.");

				// reformat
				container = _.toSlug(container);

				// get file list
				let files = await this.context.containers().list(container);

				// deletes files
				let deletes = await this.context.objects().deletes(_.map(files, (file) => {
					return "/" + container + "/" + file.name;
				}), false);

				resolve(deletes);
			} catch (e) {
				return reject(e);
			}
		});
	}

	/**
	 * Delete all objects in container and return boolean as result
	 *
	 * @param {String} container Name of container
	 *
	 * @async
	 * @return {Promise<Boolean>}
	 */
	async delete_objects_with_result(container) {
		try {
			await this.context.containers().delete_objects(container);
			return true;
		} catch (e) {
			return false;
		}
	}

	/**
	 * Delete container and all objects in container if force
	 *
	 * @param {String} container Name of container
	 * @param {Boolean=} [force=false] Boolean as true to delete object in container, default false
	 *
	 * @async
	 * @return {Promise<Object>}
	 */
	delete(container, force = false) {
		return new Promise(async (resolve, reject) => {
			try {
				if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is expected.");
				if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is not a string.");
				if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter contains special chars.");

				let files = await this.context.containers().list(container);
				let deletes = {};

				if (_.count(files) >= 1) {
					if (!force) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container has files, use force to delete container with files or delete files before.");

					deletes['files'] = await this.context.containers().delete_objects(container);
					deletes['container'] = await this._delete(container);
				} else {
					deletes = await this._delete(container);
				}

				resolve(deletes);
			} catch (e) {
				reject(e);
			}
		});
	}

	/**
	 * Delete container and all objects in container if force and return boolean as result
	 *
	 * @param {String} container Name of container
	 * @param {Boolean=} [force=false] Boolean as true to delete object in container, default false
	 *
	 * @async
	 * @return {Promise<Boolean>}
	 */
	async delete_with_result(container, force = false) {
		try {
			await this.context.containers().delete(container, force);
			return true;
		} catch (e) {
			return false;
		}
	}

	/**
	 * List of all objects in container
	 *
	 * @param {String} container Name of container
	 *
	 * @async
	 * @return {Promise<Array<Object>>}
	 */
	list(container) {
		return new Promise((resolve, reject) => {
			try {
				(async () => {
					// check
					if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name parameter is expected.");
					if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name parameter is not a string.");
					if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name parameter contains special chars.");

					// reformat
					container = _.toSlug(container);

					// check if container exist
					if (!await this.context.containers().exist(container)) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name spécified in parameter don't exist.");

					// call
					request({
						method: 'GET',
						uri: encodeURI(this.context.endpoint.url + '/' + container),
						headers: {
							"X-Auth-Token": this.context.token,
							"Accept": "application/json"
						}
					}, (err, res, body) => {
						err = err || request.checkIfResponseIsError(res);
						if (err) throw new Error(err);

						return resolve((_.isString(body) ? (_.isJSON(body) ? JSON.parse(body) : body) : body));
					});
				})();
			} catch (e) {
				return reject(e);
			}
		});
	}

	/**
	 * Check if container exist and return boolean
	 *
	 * @param {String} container Name of container
	 *
	 * @async
	 * @return {Promise<Boolean>}
	 */
	exist(container) {
		return new Promise((resolve, reject) => {
			try {
				// check
				if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is expected.");
				if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter is not a string.");
				if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
					throw new Error("Container name parameter contains special chars.");

				// reformat
				container = _.toSlug(container);

				// call
				request({
					method: 'GET',
					uri: encodeURI(this.context.endpoint.url + '/' + container),
					headers: {
						"X-Auth-Token": this.context.token,
						"Accept": "application/json"
					}
				}, (err, res, body) => {
					if (parseInt(res.statusCode) === 404) {
						return resolve(false);
					}

					err = err || request.checkIfResponseIsError(res);
					if (err) throw new Error(err);

					return resolve(true);
				});
			} catch (e) {
				return reject(e);
			}
		});
	}

	/**
	 * Get information details of container
	 *
	 * @param {String} container Name of container
	 *
	 * @async
	 * @return {Promise<Object>}
	 */
	info(container) {
		return new Promise((resolve, reject) => {
			try {
				(async () => {
					// check
					if (_.isUndefined(container)) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name parameter is expected.");
					if (!_.isString(container)) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name parameter is not a string.");
					if (_.includes(container, "/")) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name parameter contains special chars.");

					// reformat
					container = _.toSlug(container);

					// check if container exist
					if (!await this.context.containers().exist(container)) // noinspection ExceptionCaughtLocallyJS
						throw new Error("Container name spécified in parameter don't exist.");

					// call
					request({
						method: 'HEAD',
						uri: encodeURI(this.context.endpoint.url + '/' + container),
						headers: {
							"X-Auth-Token": this.context.token,
							"Accept": "application/json"
						}
					}, (err, res, body) => {
						err = err || request.checkIfResponseIsError(res);
						if (err) // noinspection ExceptionCaughtLocallyJS
							throw new Error(err);

						return resolve(res.headers);
					});
				})();
			} catch (e) {
				return reject(e);
			}
		});
	}

	/**
	 * Manage meta data of container
	 * Available methods : create(), update(), delete(), all(), has(), get()
	 *
	 * @return {ContainersMeta}
	 */
	metas() {
		return new ContainersMeta(this.context);
	}
}


module.exports = Containers;