399 lines
12 KiB
TypeScript
Raw Normal View History

2023-03-03 15:23:46 +00:00
import "reflect-metadata";
import { inject, injectable } from "tsyringe";
import crypto from "node:crypto";
2023-11-13 11:14:58 -05:00
import fs from "node:fs";
import path, { resolve } from "node:path";
2023-11-13 11:14:58 -05:00
import { promisify } from "node:util";
2023-11-13 11:43:37 -05:00
import { IAsyncQueue } from "@spt-aki/models/spt/utils/IAsyncQueue";
import { writeFileSync } from "atomically";
import lockfile from "proper-lockfile";
2023-03-03 15:23:46 +00:00
@injectable()
2023-11-07 23:29:25 -05:00
export class VFS
2023-03-03 15:23:46 +00:00
{
accessFilePromisify: (path: fs.PathLike, mode?: number) => Promise<void>;
copyFilePromisify: (src: fs.PathLike, dst: fs.PathLike, flags?: number) => Promise<void>;
2023-11-13 11:14:58 -05:00
mkdirPromisify: (path: fs.PathLike, options: fs.MakeDirectoryOptions & {recursive: true;}) => Promise<string>;
2023-03-03 15:23:46 +00:00
readFilePromisify: (path: fs.PathLike) => Promise<Buffer>;
writeFilePromisify: (path: fs.PathLike, data: string, options?: any) => Promise<void>;
2023-11-13 11:14:58 -05:00
readdirPromisify: (
path: fs.PathLike,
options?: BufferEncoding | {encoding: BufferEncoding; withFileTypes?: false;},
) => Promise<string[]>;
statPromisify: (path: fs.PathLike, options?: fs.StatOptions & {bigint?: false;}) => Promise<fs.Stats>;
2023-03-03 15:23:46 +00:00
unlinkPromisify: (path: fs.PathLike) => Promise<void>;
rmdirPromisify: (path: fs.PathLike) => Promise<void>;
renamePromisify: (oldPath: fs.PathLike, newPath: fs.PathLike) => Promise<void>;
2023-03-03 15:23:46 +00:00
constructor(@inject("AsyncQueue") protected asyncQueue: IAsyncQueue)
2023-03-03 15:23:46 +00:00
{
this.accessFilePromisify = promisify(fs.access);
this.copyFilePromisify = promisify(fs.copyFile);
this.mkdirPromisify = promisify(fs.mkdir);
this.readFilePromisify = promisify(fs.readFile);
this.writeFilePromisify = promisify(fs.writeFile);
this.readdirPromisify = promisify(fs.readdir);
this.statPromisify = promisify(fs.stat);
this.unlinkPromisify = promisify(fs.unlinkSync);
this.rmdirPromisify = promisify(fs.rmdir);
this.renamePromisify = promisify(fs.renameSync);
2023-03-03 15:23:46 +00:00
}
2023-11-07 23:29:25 -05:00
public exists(filepath: fs.PathLike): boolean
2023-03-03 15:23:46 +00:00
{
return fs.existsSync(filepath);
}
2023-11-07 23:29:25 -05:00
public async existsAsync(filepath: fs.PathLike): Promise<boolean>
2023-03-03 15:23:46 +00:00
{
2023-11-07 23:29:25 -05:00
try
2023-03-03 15:23:46 +00:00
{
// Create the command to add to the queue
const command = {uuid: crypto.randomUUID(), cmd: async () => await this.accessFilePromisify(filepath)};
2023-03-03 15:23:46 +00:00
// Wait for the command completion
await this.asyncQueue.waitFor(command);
// If no Exception, the file exists
return true;
}
2023-11-07 23:29:25 -05:00
catch
2023-03-03 15:23:46 +00:00
{
2023-11-07 23:29:25 -05:00
// If Exception, the file does not exist
2023-03-03 15:23:46 +00:00
return false;
}
}
2023-11-07 23:29:25 -05:00
public copyFile(filepath: fs.PathLike, target: fs.PathLike): void
2023-03-03 15:23:46 +00:00
{
fs.copyFileSync(filepath, target);
}
2023-11-07 23:29:25 -05:00
public async copyAsync(filepath: fs.PathLike, target: fs.PathLike): Promise<void>
2023-03-03 15:23:46 +00:00
{
const command = {uuid: crypto.randomUUID(), cmd: async () => await this.copyFilePromisify(filepath, target)};
2023-03-03 15:23:46 +00:00
await this.asyncQueue.waitFor(command);
}
2023-11-07 23:29:25 -05:00
public createDir(filepath: string): void
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:14:58 -05:00
fs.mkdirSync(filepath.substr(0, filepath.lastIndexOf("/")), {recursive: true});
2023-03-03 15:23:46 +00:00
}
2023-11-07 23:29:25 -05:00
public async createDirAsync(filepath: string): Promise<void>
2023-03-03 15:23:46 +00:00
{
const command = {
uuid: crypto.randomUUID(),
2023-11-13 11:14:58 -05:00
cmd: async () =>
await this.mkdirPromisify(filepath.substr(0, filepath.lastIndexOf("/")), {recursive: true}),
2023-03-03 15:23:46 +00:00
};
await this.asyncQueue.waitFor(command);
}
2023-11-07 23:29:25 -05:00
public copyDir(filepath: string, target: string, fileExtensions: string | string[] = undefined): void
2023-03-03 15:23:46 +00:00
{
const files = this.getFiles(filepath);
const dirs = this.getDirs(filepath);
2023-11-07 23:29:25 -05:00
if (!this.exists(target))
2023-03-03 15:23:46 +00:00
{
this.createDir(`${target}/`);
}
2023-11-07 23:29:25 -05:00
for (const dir of dirs)
2023-03-03 15:23:46 +00:00
{
this.copyDir(path.join(filepath, dir), path.join(target, dir), fileExtensions);
}
2023-11-07 23:29:25 -05:00
for (const file of files)
2023-03-03 15:23:46 +00:00
{
// copy all if fileExtension is not set, copy only those with fileExtension if set
2023-11-07 23:29:25 -05:00
if (!fileExtensions || fileExtensions.includes(file.split(".").pop()))
2023-03-03 15:23:46 +00:00
{
this.copyFile(path.join(filepath, file), path.join(target, file));
}
}
}
2023-11-07 23:29:25 -05:00
public async copyDirAsync(filepath: string, target: string, fileExtensions: string | string[]): Promise<void>
2023-03-03 15:23:46 +00:00
{
const files = this.getFiles(filepath);
const dirs = this.getDirs(filepath);
2023-11-07 23:29:25 -05:00
if (!await this.existsAsync(target))
2023-03-03 15:23:46 +00:00
{
await this.createDirAsync(`${target}/`);
}
2023-11-07 23:29:25 -05:00
for (const dir of dirs)
2023-03-03 15:23:46 +00:00
{
await this.copyDirAsync(path.join(filepath, dir), path.join(target, dir), fileExtensions);
}
2023-11-07 23:29:25 -05:00
for (const file of files)
2023-03-03 15:23:46 +00:00
{
// copy all if fileExtension is not set, copy only those with fileExtension if set
2023-11-07 23:29:25 -05:00
if (!fileExtensions || fileExtensions.includes(file.split(".").pop()))
2023-03-03 15:23:46 +00:00
{
await this.copyAsync(path.join(filepath, file), path.join(target, file));
}
}
}
2023-11-07 23:29:25 -05:00
public readFile(...args: Parameters<typeof fs.readFileSync>): string
2023-03-03 15:23:46 +00:00
{
const read = fs.readFileSync(...args);
if (this.isBuffer(read))
2023-11-13 11:14:58 -05:00
{
2023-03-03 15:23:46 +00:00
return read.toString();
2023-11-13 11:14:58 -05:00
}
2023-03-03 15:23:46 +00:00
return read;
}
2023-11-07 23:29:25 -05:00
public async readFileAsync(path: fs.PathLike): Promise<string>
2023-03-03 15:23:46 +00:00
{
const read = await this.readFilePromisify(path);
if (this.isBuffer(read))
2023-11-13 11:14:58 -05:00
{
2023-03-03 15:23:46 +00:00
return read.toString();
2023-11-13 11:14:58 -05:00
}
2023-03-03 15:23:46 +00:00
return read;
}
private isBuffer(value: any): value is Buffer
{
return value?.write && value.toString && value.toJSON && value.equals;
}
2023-11-07 23:29:25 -05:00
public writeFile(filepath: any, data = "", append = false, atomic = true): void
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:14:58 -05:00
const options = append ? {flag: "a"} : {flag: "w"};
2023-03-03 15:23:46 +00:00
2023-11-07 23:29:25 -05:00
if (!this.exists(filepath))
2023-03-03 15:23:46 +00:00
{
this.createDir(filepath);
fs.writeFileSync(filepath, "");
}
this.lockFileSync(filepath);
2023-11-07 23:29:25 -05:00
if (!append && atomic)
2023-03-03 15:23:46 +00:00
{
writeFileSync(filepath, data);
}
2023-11-07 23:29:25 -05:00
else
2023-03-03 15:23:46 +00:00
{
fs.writeFileSync(filepath, data, options);
}
2023-11-07 23:29:25 -05:00
if (this.checkFileSync(filepath))
2023-03-03 15:23:46 +00:00
{
this.unlockFileSync(filepath);
}
}
2023-11-07 23:29:25 -05:00
public async writeFileAsync(filepath: any, data = "", append = false, atomic = true): Promise<void>
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:14:58 -05:00
const options = append ? {flag: "a"} : {flag: "w"};
2023-03-03 15:23:46 +00:00
2023-11-07 23:29:25 -05:00
if (!await this.exists(filepath))
2023-03-03 15:23:46 +00:00
{
await this.createDir(filepath);
await this.writeFilePromisify(filepath, "");
}
2023-11-07 23:29:25 -05:00
if (!append && atomic)
2023-03-03 15:23:46 +00:00
{
await this.writeFilePromisify(filepath, data);
}
2023-11-07 23:29:25 -05:00
else
2023-03-03 15:23:46 +00:00
{
await this.writeFilePromisify(filepath, data, options);
}
}
2023-11-07 23:29:25 -05:00
public getFiles(filepath: string): string[]
2023-03-03 15:23:46 +00:00
{
2023-11-07 23:29:25 -05:00
return fs.readdirSync(filepath).filter((item) =>
2023-03-03 15:23:46 +00:00
{
return fs.statSync(path.join(filepath, item)).isFile();
});
}
2023-11-07 23:29:25 -05:00
public async getFilesAsync(filepath: string): Promise<string[]>
2023-03-03 15:23:46 +00:00
{
const addr = await this.readdirPromisify(filepath);
2023-11-07 23:29:25 -05:00
return addr.filter(async (item) =>
2023-03-03 15:23:46 +00:00
{
const stat = await this.statPromisify(path.join(filepath, item));
return stat.isFile();
});
}
2023-11-07 23:29:25 -05:00
public getDirs(filepath: string): string[]
2023-03-03 15:23:46 +00:00
{
2023-11-07 23:29:25 -05:00
return fs.readdirSync(filepath).filter((item) =>
2023-03-03 15:23:46 +00:00
{
return fs.statSync(path.join(filepath, item)).isDirectory();
});
}
2023-11-07 23:29:25 -05:00
public async getDirsAsync(filepath: string): Promise<string[]>
2023-03-03 15:23:46 +00:00
{
const addr = await this.readdirPromisify(filepath);
2023-11-07 23:29:25 -05:00
return addr.filter(async (item) =>
2023-03-03 15:23:46 +00:00
{
const stat = await this.statPromisify(path.join(filepath, item));
return stat.isDirectory();
});
}
2023-11-07 23:29:25 -05:00
public removeFile(filepath: string): void
2023-03-03 15:23:46 +00:00
{
fs.unlinkSync(filepath);
}
2023-11-07 23:29:25 -05:00
public async removeFileAsync(filepath: string): Promise<void>
2023-03-03 15:23:46 +00:00
{
await this.unlinkPromisify(filepath);
}
2023-11-07 23:29:25 -05:00
public removeDir(filepath: string): void
2023-03-03 15:23:46 +00:00
{
const files = this.getFiles(filepath);
const dirs = this.getDirs(filepath);
2023-11-07 23:29:25 -05:00
for (const dir of dirs)
2023-03-03 15:23:46 +00:00
{
this.removeDir(path.join(filepath, dir));
}
2023-11-07 23:29:25 -05:00
for (const file of files)
2023-03-03 15:23:46 +00:00
{
this.removeFile(path.join(filepath, file));
}
fs.rmdirSync(filepath);
}
2023-11-07 23:29:25 -05:00
public async removeDirAsync(filepath: string): Promise<void>
2023-03-03 15:23:46 +00:00
{
const files = this.getFiles(filepath);
const dirs = this.getDirs(filepath);
const promises = [];
2023-11-07 23:29:25 -05:00
for (const dir of dirs)
2023-03-03 15:23:46 +00:00
{
promises.push(this.removeDirAsync(path.join(filepath, dir)));
}
2023-11-07 23:29:25 -05:00
for (const file of files)
2023-03-03 15:23:46 +00:00
{
promises.push(this.removeFile(path.join(filepath, file)));
}
await Promise.all(promises);
await this.rmdirPromisify(filepath);
}
2023-11-07 23:29:25 -05:00
public rename(oldPath: string, newPath: string): void
{
fs.renameSync(oldPath, newPath);
}
2023-11-07 23:29:25 -05:00
public async renameAsync(oldPath: string, newPath: string): Promise<void>
{
await this.renamePromisify(oldPath, newPath);
}
2023-11-07 23:29:25 -05:00
protected lockFileSync(filepath: any): void
2023-03-03 15:23:46 +00:00
{
lockfile.lockSync(filepath);
}
2023-11-07 23:29:25 -05:00
protected checkFileSync(filepath: any): any
2023-03-03 15:23:46 +00:00
{
return lockfile.checkSync(filepath);
}
2023-11-07 23:29:25 -05:00
protected unlockFileSync(filepath: any): void
2023-03-03 15:23:46 +00:00
{
lockfile.unlockSync(filepath);
}
2023-11-07 23:29:25 -05:00
public getFileExtension(filepath: string): string
2023-03-03 15:23:46 +00:00
{
return filepath.split(".").pop();
}
2023-11-07 23:29:25 -05:00
public stripExtension(filepath: string): string
2023-03-03 15:23:46 +00:00
{
return filepath.split(".").slice(0, -1).join(".");
}
2023-11-07 23:29:25 -05:00
public async minifyAllJsonInDirRecursive(filepath: string): Promise<void>
2023-03-03 15:23:46 +00:00
{
const files = this.getFiles(filepath).filter((item) => this.getFileExtension(item) === "json");
2023-11-07 23:29:25 -05:00
for (const file of files)
2023-03-03 15:23:46 +00:00
{
const filePathAndName = path.join(filepath, file);
const minified = JSON.stringify(JSON.parse(this.readFile(filePathAndName)));
this.writeFile(filePathAndName, minified);
}
const dirs = this.getDirs(filepath);
2023-11-07 23:29:25 -05:00
for (const dir of dirs)
2023-03-03 15:23:46 +00:00
{
this.minifyAllJsonInDirRecursive(path.join(filepath, dir));
}
}
2023-11-07 23:29:25 -05:00
public async minifyAllJsonInDirRecursiveAsync(filepath: string): Promise<void>
2023-03-03 15:23:46 +00:00
{
const files = this.getFiles(filepath).filter((item) => this.getFileExtension(item) === "json");
2023-11-07 23:29:25 -05:00
for (const file of files)
2023-03-03 15:23:46 +00:00
{
const filePathAndName = path.join(filepath, file);
const minified = JSON.stringify(JSON.parse(await this.readFile(filePathAndName)));
await this.writeFile(filePathAndName, minified);
}
const dirs = this.getDirs(filepath);
const promises: Promise<void>[] = [];
2023-11-07 23:29:25 -05:00
for (const dir of dirs)
2023-03-03 15:23:46 +00:00
{
promises.push(this.minifyAllJsonInDirRecursive(path.join(filepath, dir)));
}
await Promise.all(promises);
}
public getFilesOfType(directory: string, fileType: string, files: string[] = []): string[]
{
// no dir so exit early
if (!fs.existsSync(directory))
{
return files;
}
2023-11-13 11:14:58 -05:00
const dirents = fs.readdirSync(directory, {encoding: "utf-8", withFileTypes: true});
2023-03-03 15:23:46 +00:00
for (const dirent of dirents)
{
const res = resolve(directory, dirent.name);
if (dirent.isDirectory())
{
this.getFilesOfType(res, fileType, files);
}
else
{
if (res.endsWith(fileType))
{
files.push(res);
}
}
}
return files;
}
}