import { inject, injectable } from "tsyringe"; import { Queue } from "@spt-aki/utils/collections/queue/Queue"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { VFS } from "@spt-aki/utils/VFS"; /* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/brace-style */ @injectable() export class ImporterUtil { constructor( @inject("VFS") protected vfs: VFS, @inject("JsonUtil") protected jsonUtil: JsonUtil ) {} /** * Load files into js objects recursively (asynchronous) * @param filepath Path to folder with files * @returns Promise return T type associated with this class */ public async loadRecursiveAsync( filepath: string, onReadCallback: (fileWithPath: string, data: string) => void = () => {}, onObjectDeserialized: (fileWithPath: string, object: any) => void = () => {} ): Promise { const result = {} as T; // get all filepaths const files = this.vfs.getFiles(filepath); const directories = this.vfs.getDirs(filepath); // add file content to result for (const file of files) { if (this.vfs.getFileExtension(file) === "json") { const filename = this.vfs.stripExtension(file); const filePathAndName = `${filepath}${file}`; await this.vfs.readFileAsync(filePathAndName).then(fileData => { onReadCallback(filePathAndName, fileData); const fileDeserialized = this.jsonUtil.deserializeWithCacheCheck(fileData, filePathAndName); onObjectDeserialized(filePathAndName, fileDeserialized); result[filename] = fileDeserialized; }); } } // deep tree search for (const dir of directories) { result[dir] = this.loadRecursiveAsync(`${filepath}${dir}/`); } // set all loadRecursive to be executed asynchronously const resEntries = Object.entries(result); const resResolved = await Promise.all(resEntries.map(ent => ent[1])); for (let resIdx = 0; resIdx < resResolved.length; resIdx++) { resEntries[resIdx][1] = resResolved[resIdx]; } // return the result of all async fetch return Object.fromEntries(resEntries) as T; } /** * Load files into js objects recursively (synchronous) * @param filepath Path to folder with files * @returns */ public loadRecursive( filepath: string, onReadCallback: (fileWithPath: string, data: string) => void = () => {}, onObjectDeserialized: (fileWithPath: string, object: any) => void = () => {} ): T { const result = {} as T; // get all filepaths const files = this.vfs.getFiles(filepath); const directories = this.vfs.getDirs(filepath); // add file content to result for (const file of files) { if (this.vfs.getFileExtension(file) === "json") { const filename = this.vfs.stripExtension(file); const filePathAndName = `${filepath}${file}`; const fileData = this.vfs.readFile(filePathAndName); onReadCallback(filePathAndName, fileData); const fileDeserialized = this.jsonUtil.deserializeWithCacheCheck(fileData, filePathAndName); onObjectDeserialized(filePathAndName, fileDeserialized); result[filename] = fileDeserialized; } } // deep tree search for (const dir of directories) { result[dir] = this.loadRecursive(`${filepath}${dir}/`); } return result; } public async loadAsync( filepath: string, strippablePath = "", onReadCallback: (fileWithPath: string, data: string) => void = () => {}, onObjectDeserialized: (fileWithPath: string, object: any) => void = () => {} ): Promise { const directoriesToRead = new Queue(); const filesToProcess = new Queue(); const promises = new Array>(); const result = {} as T; const files = this.vfs.getFiles(filepath); const directories = this.vfs.getDirs(filepath); directoriesToRead.enqueueAll(directories.map(d => `${filepath}${d}`)); filesToProcess.enqueueAll(files.map(f => new VisitNode(filepath, f))); while (!directoriesToRead.isEmpty()) { const directory = directoriesToRead.dequeue(); filesToProcess.enqueueAll(this.vfs.getFiles(directory).map(f => new VisitNode(`${directory}/`, f))); directoriesToRead.enqueueAll(this.vfs.getDirs(directory).map(d => `${directory}/${d}`)); } while (!filesToProcess.isEmpty()) { const fileNode = filesToProcess.dequeue(); if (this.vfs.getFileExtension(fileNode.fileName) === "json") { const filePathAndName = `${fileNode.filePath}${fileNode.fileName}`; promises.push( this.vfs.readFileAsync(filePathAndName) .then(async fileData => { onReadCallback(filePathAndName, fileData); return this.jsonUtil.deserializeWithCacheCheckAsync(fileData, filePathAndName); }) .then(async fileDeserialized => { onObjectDeserialized(filePathAndName, fileDeserialized); const strippedFilePath = this.vfs.stripExtension(filePathAndName).replace(filepath, ""); this.placeObject(fileDeserialized, strippedFilePath, result, strippablePath); }) ); } } await Promise.all(promises).catch(e => console.error(e)); return result; } protected placeObject(fileDeserialized: any, strippedFilePath: string, result: T, strippablePath: string):void { const strippedFinalPath = strippedFilePath.replace(strippablePath, ""); let temp = result; const propertiesToVisit = strippedFinalPath.split("/"); for (let i = 0; i < propertiesToVisit.length; i++) { const property = propertiesToVisit[i]; if (i === (propertiesToVisit.length - 1)) { temp[property] = fileDeserialized; } else { if (!temp[property]) temp[property] = {}; temp = temp[property]; } } } } class VisitNode { constructor( public filePath: string, public fileName: string ){} }