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
JavaScript
"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