UNPKG

tspace-nfs

Version:

tspace-nfs is a Network File System (NFS) and provides both server and client capabilities for accessing files over a network.

407 lines 17.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Utils = void 0; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const fs_extra_1 = __importDefault(require("fs-extra")); const tspace_utils_1 = require("tspace-utils"); class Utils { constructor(buckets, rootFolder, metadata, trash) { this.buckets = buckets; this.rootFolder = rootFolder; this.metadata = metadata; this.trash = trash; this.backup = 30; this.safelyParseJSON = (v) => { try { return JSON.parse(v); } catch (err) { return v; } }; this.removeDir = (path) => __awaiter(this, void 0, void 0, function* () { return yield fs_1.default.promises .rm(path, { recursive: true }) .catch(_ => { return; }); }); this._getContentType = (extension) => { switch (String(extension).toLowerCase()) { case 'txt': return 'text/plain'; case 'html': case 'htm': return 'text/html'; case 'css': return 'text/css'; case 'js': return 'application/javascript'; case 'json': return 'application/json'; case 'xml': return 'application/xml'; case 'pdf': return 'application/pdf'; case 'doc': case 'docx': return 'application/msword'; case 'xls': case 'xlsx': return 'application/vnd.ms-excel'; case 'ppt': case 'pptx': return 'application/vnd.ms-powerpoint'; case 'jpg': case 'jpeg': return 'image/jpeg'; case 'png': return 'image/png'; case 'gif': return 'image/gif'; case 'bmp': return 'image/bmp'; case 'svg': return 'image/svg+xml'; case 'mp3': return 'audio/mpeg'; case 'wav': return 'audio/wav'; case 'ogg': return 'audio/ogg'; case 'mp4': return 'video/mp4'; case 'avi': return 'video/x-msvideo'; case 'mpeg': return 'video/mpeg'; case 'zip': return 'application/zip'; case 'rar': return 'application/x-rar-compressed'; case 'tar': return 'application/x-tar'; case 'gz': return 'application/gzip'; case '7z': return 'application/x-7z-compressed'; default: return 'application/octet-stream'; } }; this.getMetadata = (bucket) => __awaiter(this, void 0, void 0, function* () { const directory = path_1.default.join(path_1.default.resolve(), `${this.rootFolder}/${bucket}/${this.metadata}`); const checkMetadata = fs_1.default.existsSync(directory); if (!checkMetadata) return null; const metadata = yield fs_1.default.promises.readFile(directory, 'utf-8'); return this.safelyParseJSON(metadata); }); this.removeOldDirInTrash = (bucket) => __awaiter(this, void 0, void 0, function* () { const directory = this.normalizeDirectory({ bucket, folder: this.trash }); const files = fs_1.default.readdirSync(directory); for (const file of files) { const dir = this.normalizePath({ directory, path: file, full: true }); const stats = yield fs_1.default.promises.stat(dir); if (!stats.isDirectory()) continue; const format = file.match(/^\d{4}-\d{2}-\d{2}/); const folderDate = new tspace_utils_1.Time(format ? format[0] : 0).toTimestamp(); const ago = new tspace_utils_1.Time().minusDays(this.backup).toTimeStamp(); if (Number.isNaN(folderDate) || folderDate > ago) continue; yield this.removeDir(dir); } }); this.syncMetadata = (syncBuckets = '*') => __awaiter(this, void 0, void 0, function* () { const rootFolder = this.rootFolder; const buckets = (this.buckets == null ? fs_1.default.readdirSync(path_1.default.join(path_1.default.resolve(), rootFolder)).filter((name) => { return fs_1.default.statSync(path_1.default.join(rootFolder, name)).isDirectory(); }) : yield this.buckets()); const analyzeDirectory = (dirPath) => __awaiter(this, void 0, void 0, function* () { let fileCount = 0; let folderCount = 0; let totalSize = 0; const traverseDirectory = (currentPath) => __awaiter(this, void 0, void 0, function* () { const items = yield fs_1.default.promises.readdir(currentPath).catch(_ => []); for (const item of items) { const itemPath = path_1.default.join(currentPath, item); const stats = yield fs_1.default.promises.stat(itemPath).catch(_ => null); if (stats == null) continue; if (stats.isDirectory() && item === this.trash) { continue; } if (stats.isDirectory()) { folderCount++; yield traverseDirectory(itemPath); } if (stats.isFile() && item !== this.metadata) { fileCount++; totalSize += stats.size; } } }); yield traverseDirectory(path_1.default.join(dirPath)); return { fileCount, folderCount, totalSize }; }); for (const bucket of buckets) { if (syncBuckets === '*' || syncBuckets === bucket) { const targetDir = `${rootFolder}/${bucket}`; const result = yield analyzeDirectory(targetDir); // write file metadata to bucket fs_1.default.writeFileSync(`${targetDir}/${this.metadata}`, JSON.stringify({ bucket, info: { files: result.fileCount, folders: result.folderCount, size: result.totalSize, sizes: { bytes: result.totalSize, kb: (result.totalSize / 1024), mb: (result.totalSize / (1024 * 1024)), gb: (result.totalSize / (1024 * 1024 * 1024)), } } }, null, 2), 'utf-8'); } } }); this.fileStructure = (dirPath, { includeFiles = false, bucket }) => __awaiter(this, void 0, void 0, function* () { const items = []; const files = yield fs_1.default.promises.readdir(dirPath).catch(_ => []); for (const file of files) { if (file === this.metadata) continue; const path = path_1.default.join(dirPath, file); const fullPath = path_1.default.join(path_1.default.resolve(), dirPath, file); const stats = yield fs_1.default.promises.lstat(fullPath).catch(_ => null); if (stats == null) continue; const lastModified = stats.mtime; if (stats === null || stats === void 0 ? void 0 : stats.isDirectory()) { items.push({ name: file, path: path.replace(/\\/g, '/').replace(`${this.rootFolder}/`, ''), isFolder: true, lastModified, size: null, extension: 'folder' }); continue; } if (!includeFiles) continue; const extension = path_1.default.extname(file).replace(/\./g, ''); items.push({ name: file, path: path.replace(/\\/g, '/').replace(this.rootFolder, ''), isFolder: false, lastModified, size: stats.size, extension }); } return items; }); } setRootFolder(folder) { this.rootFolder = folder; return this; } setMetaData(meta) { this.metadata = meta; return this; } remove(path, { delayMs = 1000 * 60 * 60 } = {}) { if (delayMs === 0) { fs_1.default.promises.unlink(path).catch(err => console.log(err)); return; } setTimeout(() => { fs_1.default.promises.unlink(path).catch(err => console.log(err)); }, delayMs); return; } trashed({ path, bucket }) { return __awaiter(this, void 0, void 0, function* () { const folder = `${this.trash}/${new tspace_utils_1.Time().onlyDate().toString()}`; const directory = this.normalizeDirectory({ bucket, folder }); const newPath = this.normalizePath({ directory, path, full: true }); const currentPath = this.normalizePath({ directory: this.normalizeDirectory({ bucket, folder: null }), path, full: true }); const newDirectory = path_1.default.dirname(newPath); if (!(yield this.fileExists(newDirectory))) { yield fs_1.default.promises.mkdir(newDirectory, { recursive: true }); } yield fs_1.default.promises .rename(currentPath, newPath) .catch(err => { console.log(err); return; }); yield this.syncMetadata(bucket); return; }); } normalizeFolder(folder) { return folder.replace(/^\/+/, '').replace(/[?#]/g, ''); } normalizeDirectory({ bucket, folder }) { return folder == null ? `${this.rootFolder}/${bucket}` : `${this.rootFolder}/${bucket}/${this.normalizeFolder(folder)}`; } normalizePath({ directory, path, full = false }) { path = path.replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const normalized = full ? directory == null ? path_1.default.join(path_1.default.resolve(), `${path}`) : path_1.default.join(path_1.default.resolve(), `${directory}/${path}`) : directory == null ? `${path}` : `${directory}/${path}`; return normalized; } makeStream({ bucket, filePath, range, download = false }) { var _a; return __awaiter(this, void 0, void 0, function* () { const directory = this.normalizeDirectory({ bucket, folder: null }); const path = this.normalizePath({ directory, path: filePath, full: true }); const contentType = this._getContentType(String((_a = filePath === null || filePath === void 0 ? void 0 : filePath.split('.')) === null || _a === void 0 ? void 0 : _a.pop())); const stat = fs_1.default.statSync(path); if (stat.isDirectory()) { throw new Error(`The stream is not support directory`); } const fileSize = stat.size; const set = (header, filePath, code = 200) => { const extension = filePath.split('.').pop(); const previews = [ 'ogv', 'ogg', 'webm', 'wav', 'mp3', 'mp4', 'pdf', 'png', 'jpeg', 'jpg', 'gif', 'webp', 'svg', 'ico' ]; return (res) => { if (previews.some(p => (extension === null || extension === void 0 ? void 0 : extension.toLocaleLowerCase()) === p)) { res.writeHead(download ? code : 206, header); return; } if (download) { res.setHeader('Content-Disposition', `attachment; filename=${+new Date()}.${extension}`); res.setHeader('Content-Type', 'application/octet-stream'); } }; }; if (contentType !== 'video/mp4') { const header = { 'Content-Length': fileSize, 'Content-Type': contentType, }; return { stream: fs_1.default.createReadStream(path), header, set: set(header, filePath) }; } if (range == null) { const header = { 'Content-Length': fileSize, 'Content-Type': contentType }; return { stream: fs_1.default.createReadStream(path) .on('error', (err) => { throw err; }), header, set: set(header, filePath) }; } const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; const chunksize = (end - start) + 1; const stream = fs_1.default.createReadStream(path, { start, end }) .on('error', (err) => { throw err; }); const header = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': contentType }; return { stream, header, set: set(header, filePath, 206) }; }); } trashedWithFolder({ path, bucket }) { return __awaiter(this, void 0, void 0, function* () { const folder = `${this.trash}/${new tspace_utils_1.Time().onlyDate().toString()}`; const directory = this.normalizeDirectory({ bucket, folder }); const newPath = this.normalizePath({ directory, path, full: true }); const currentPath = this.normalizePath({ directory: this.normalizeDirectory({ bucket, folder: null }), path, full: true }); if (!(yield this.fileExists(newPath))) { yield fs_1.default.promises.mkdir(newPath, { recursive: true }); } yield fs_extra_1.default .copy(currentPath, newPath) .then(_ => { fs_1.default.rmSync(currentPath, { recursive: true, force: true }); }) .catch(err => { console.log(err); return; }); yield this.syncMetadata(bucket); return; }); } fileExists(path) { return __awaiter(this, void 0, void 0, function* () { try { yield fs_1.default.promises.stat(path); return true; } catch (err) { return false; } }); } files(dir, { ignore = null } = {}) { return __awaiter(this, void 0, void 0, function* () { const directories = yield fs_1.default.promises.readdir(dir, { withFileTypes: true }).catch(_ => []); const files = yield Promise.all(directories.map((directory) => { const newDir = path_1.default.resolve(String(dir), directory.name); if (directory.isDirectory() && (ignore != null && directory.name === ignore)) { return null; } return directory.isDirectory() ? this.files(newDir) : newDir; })); return [].concat(...files.filter(Boolean)); }); } } exports.Utils = Utils; //# sourceMappingURL=index.js.map