UNPKG

@egodigital/egoose

Version:

Helper classes and functions for Node.js 10 or later.

374 lines 13.9 kB
"use strict"; /** * This file is part of the @egodigital/egoose distribution. * Copyright (c) e.GO Digital GmbH, Aachen, Germany (https://www.e-go-digital.com/) * * @egodigital/egoose is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, version 3. * * @egodigital/egoose is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeAzureBlobPath = exports.AzureStorageClient = void 0; const _ = require("lodash"); const azureStorage = require("azure-storage"); const crypto = require("crypto"); const Enumerable = require("node-enumerable"); const isStream = require("is-stream"); const mimeTypes = require("mime-types"); const sanitizeFilename = require('sanitize-filename'); const streams_1 = require("../streams"); const index_1 = require("../index"); const fs_extra_1 = require("fs-extra"); const path_1 = require("path"); const fs_1 = require("../fs"); const NO_CONTINUE_TOKEN_YET = Symbol('NO_CONTINUE_TOKEN_YET'); /** * An async Azure Storage client. */ class AzureStorageClient { /** * Initializes a new instance of that class. * * @param {AzureStorageClientOptions} [options] The custom options. */ constructor(options) { this.options = options; if (_.isNil(this.options)) { this.options = {}; } } /** * Creates a new blob service instance, based on the underlying options. * * @return {Promise<azureStorage.BlobService>} The promise with the new instance. */ async createBlobService() { let provider = this.options.blobServiceProvider; if (_.isNil(provider)) { // use default provider = () => azureStorage.createBlobService(); } return Promise.resolve(provider()); } /** * Creates a read stream for a blob. * * @param {string} path The path of the blob. * * @return {Promise<Readable>} The promise with the stream. */ createBlobReadStream(path) { return new Promise(async (resolve, reject) => { try { const CONTAINER = index_1.toStringSafe(await this.getContainer()); const BLOB_NAME = index_1.toStringSafe(await this.toFullPath(path)); const BLOBS = await this.createBlobService(); let stream; stream = BLOBS.createReadStream(CONTAINER, BLOB_NAME, (err, result) => { if (err) { reject(err); } else { resolve(stream); } }); } catch (e) { reject(e); } }); } /** * Creates a new instance from environment settings. * * @return {AzureStorageClient} The new instance. */ static fromEnvironment() { return new AzureStorageClient(); } /** * Tries to return information about a blob. * * @param {string} path The path / name of the blob to check. * * @return {Promise<false|azureStorage.BlobService.BlobResult>} The promise that contains the blob information or (false) if it does not exist. */ getBlobInfo(path) { return new Promise(async (resolve, reject) => { try { const BLOBS = await this.createBlobService(); const CONTAINER = index_1.toStringSafe(await this.getContainer()); const BLOB_NAME = index_1.toStringSafe(await this.toFullPath(path)); BLOBS.doesBlobExist(CONTAINER, BLOB_NAME, (err, result) => { if (err) { reject(err); } else { if (index_1.toBooleanSafe(result.exists)) { resolve(result); } else { resolve(false); } } }); } catch (e) { reject(e); } }); } getContainer() { let containerProvider = this.options.blobContainerProvider; if (_.isNil(containerProvider)) { // use default containerProvider = () => process.env .AZURE_STORAGE_CONTAINER .trim(); } else { if (!_.isFunction(containerProvider)) { const CONTAINER_PROVIDER = index_1.toStringSafe(containerProvider); containerProvider = () => { return CONTAINER_PROVIDER; }; } } return Promise.resolve(containerProvider()); } /** * Lists a folder in a blob storage container. * * @param {string} path The path of the folder. * * @return {Promise<azureStorage.BlobService.BlobResult[]>} The promise with the result. */ listBlobs(path) { path = toFullBlobPath(path) + '/'; return new Promise(async (resolve, reject) => { try { const BLOB_RESULTS = []; const COMPLETED = (err) => { if (err) { reject(err); } else { resolve(Enumerable.from(BLOB_RESULTS).where(br => { const KEY = index_1.normalizeString(br.name); return '' !== KEY && '/' !== KEY; }).orderBy(br => { return index_1.normalizeString(br.name); }).toArray()); } }; const BLOBS = await this.createBlobService(); const CONTAINER = index_1.toStringSafe(await this.getContainer()); let currentContinuationToken = NO_CONTINUE_TOKEN_YET; const HANDLE_RESULT = (result) => { currentContinuationToken = undefined; if (!result) { return; } currentContinuationToken = result.continuationToken; index_1.asArray(result.entries).forEach(e => { BLOB_RESULTS.push(e); }); }; const NEXT_SEGMENT = () => { try { if (NO_CONTINUE_TOKEN_YET !== currentContinuationToken) { if (!currentContinuationToken) { COMPLETED(null); return; } } else { currentContinuationToken = undefined; } BLOBS.listBlobsSegmentedWithPrefix(CONTAINER, path, currentContinuationToken, {}, (err, result) => { if (err) { COMPLETED(err); } else { HANDLE_RESULT(result); NEXT_SEGMENT(); } }); } catch (e) { COMPLETED(e); } }; NEXT_SEGMENT(); } catch (e) { reject(e); } }); } /** * Loads a blob. * * @param {string} path The path / blob name. * * @return {Promise<Buffer>} The promises with the loaded data. */ loadBlob(path) { return new Promise(async (resolve, reject) => { try { const BLOBS = await this.createBlobService(); const DATA = await fs_1.tempFile((tmpFile) => { return new Promise(async (res, rej) => { try { const STREAM = fs_extra_1.createWriteStream(tmpFile); const CONTAINER = index_1.toStringSafe(await this.getContainer()); const BLOB_NAME = index_1.toStringSafe(await this.toFullPath(path)); BLOBS.getBlobToStream(CONTAINER, BLOB_NAME, STREAM, (err) => { if (err) { rej(err); } else { try { res(fs_extra_1.readFileSync(tmpFile)); } catch (e) { rej(e); } } }); } catch (e) { rej(e); } }); }); resolve(DATA); } catch (e) { reject(e); } }); } /** * Saves / uploads a blob. * * @param {string} path The path / name of the blob. * @param {any} data The data to upload / store. */ saveBlob(path, data) { return new Promise(async (resolve, reject) => { try { const BLOBS = await this.createBlobService(); let mimeDetector = this.options.blobContentTypeDetector; if (_.isNil(mimeDetector)) { // use default mimeDetector = (p) => { let mime = mimeTypes.lookup(p); if (false === mime) { mime = 'application/octet-stream'; } return mime; }; } const BLOB_NAME = index_1.toStringSafe(await this.toFullPath(path)); const CONTENT_TYPE = index_1.toStringSafe(await Promise.resolve(mimeDetector(BLOB_NAME))); const CONTAINER = index_1.toStringSafe(await this.getContainer()); let dataToStore; if (_.isNil(data)) { dataToStore = Buffer.alloc(0); } else { if (Buffer.isBuffer(data)) { dataToStore = data; } else if (isStream.readable(data)) { dataToStore = await streams_1.readAll(data); } else { dataToStore = Buffer.from(index_1.toStringSafe(data), 'utf8'); } } BLOBS.createBlockBlobFromText(CONTAINER, BLOB_NAME, dataToStore, { contentSettings: { contentMD5: crypto.createHash('md5') .update(data).digest('base64'), contentType: CONTENT_TYPE, }, }, (err) => { if (err) { reject(err); } else { resolve(); } }); } catch (e) { reject(e); } }); } /** * Saves a blob with a unique name / path. * * @param {string} path The original path / name of the blob. * @param {any} data The data to store / save. * * @return {Promise<string>} The promise with the path / name of the stored blob. */ async saveUniqueBlob(path, data) { let blobNameCreator = this.options.uniqueBlobNameCreator; if (_.isNil(blobNameCreator)) { // use default blobNameCreator = (orgName) => { const BLOB_DIR = path_1.dirname(orgName); const BLOB_EXT = path_1.extname(orgName); const BLOB_NAME = `${index_1.uuid().split('-').join('')}_${Math.round(Math.random() * 597923979)}_tmmk`; return path_1.join(BLOB_DIR, BLOB_NAME + BLOB_EXT); }; } const BLOB_NAME_NEW = index_1.toStringSafe(await Promise.resolve(blobNameCreator(index_1.toStringSafe(path)))); await this.saveBlob(BLOB_NAME_NEW, data); return await this.toFullPath(BLOB_NAME_NEW); } toFullPath(p) { let tfp = this.options.toFullBlobPath; if (_.isNil(tfp)) { // use default tfp = toFullBlobPath; } return Promise.resolve(tfp(p)); } } exports.AzureStorageClient = AzureStorageClient; /** * Normalizes an Azure blob path. * * @param {string} p The input path. * * @return {string} The normalized path. */ function normalizeAzureBlobPath(p) { return index_1.toStringSafe(p).trim(); } exports.normalizeAzureBlobPath = normalizeAzureBlobPath; function toFullBlobPath(p) { let prefix = sanitizeFilename(index_1.normalizeString(process.env.APP_ENV)); if ('' === prefix) { prefix = 'prod'; } let fullPath = prefix + '/' + normalizeAzureBlobPath(p); fullPath = fullPath.split(path_1.sep).join('/'); return fullPath; } //# sourceMappingURL=storage.js.map