@egodigital/egoose
Version:
Helper classes and functions for Node.js 10 or later.
374 lines • 13.9 kB
JavaScript
;
/**
* 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