import { downloadBinaryFilePercent, DownloadPercentHandler } from "./download"; import { parseZipFile, ProgressHandler } from "./zip-file"; import idbfsModule, { MainModule } from "@irori/idbfs"; function fetchFilesystemZip(progressCallback: DownloadPercentHandler) { return downloadBinaryFilePercent("./root.zip", progressCallback); } async function fetchFilesystem( progressHandler: ProgressHandler, downloadProgressHandler: DownloadPercentHandler, ) { const filesys = await fetchFilesystemZip(downloadProgressHandler); return await parseZipFile(filesys, progressHandler); } function synchronizeIDBFS(idbfs: MainModule, populate: boolean) { return new Promise((resolve, reject) => { idbfs.FS.syncfs(populate, function (err: any) { if (err) { reject(err); } else { resolve(); } }); }); } const filesystemPrefix = "/root/filesys/"; export function internalToWindowsPath(internalPath: string): string { if ( !internalPath.startsWith(filesystemPrefix) || internalPath.length <= filesystemPrefix.length ) { throw new Error("Invalid path"); } const winPath = internalPath.substring(filesystemPrefix.length); return `${winPath[0]}:${winPath.substring(1)}`; } export function windowsToInternalPath(windowsPath: string): string { if (windowsPath.length < 2 || windowsPath[1] != ":") { throw new Error("Invalid path"); } return `${filesystemPrefix}${windowsPath[0]}${windowsPath.substring(2)}`; } async function initializeIDBFS() { const idbfs = await idbfsModule(); idbfs.FS.mkdir("/root"); idbfs.FS.mount(idbfs.IDBFS, {}, "/root"); await synchronizeIDBFS(idbfs, true); return idbfs; } export interface FileWithData { name: string; data: ArrayBuffer; } function deleteDatabase(dbName: string) { return new Promise((resolve, reject) => { const request = indexedDB.deleteDatabase(dbName); request.onsuccess = () => { resolve(); }; request.onerror = () => { reject(new Error(`Error deleting database ${dbName}.`)); }; request.onblocked = () => { reject(new Error(`Deletion of database ${dbName} blocked.`)); }; }); } function filterPseudoDir(e: string) { return e != "." && e != ".."; } export class Filesystem { private idbfs: MainModule; constructor(idbfs: MainModule) { this.idbfs = idbfs; } _storeFile(file: FileWithData) { if (file.name.includes("/")) { const folder = file.name.split("/").slice(0, -1).join("/"); this._createFolder(folder); } const buffer = new Uint8Array(file.data); this.idbfs.FS.writeFile(file.name, buffer); } readFile(file: string): Uint8Array { return this.idbfs.FS.readFile(file); } async storeFiles(files: FileWithData[]) { files.forEach((f) => { this._storeFile(f); }); await this.sync(); } _unlinkRecursive(element: string) { if (!this.isFolder(element)) { this.idbfs.FS.unlink(element); return; } this.readDir(element) // .filter(filterPseudoDir) .forEach((e) => { this._unlinkRecursive(`${element}/${e}`); }); this.idbfs.FS.rmdir(element); } async rename(oldFile: string, newFile: string) { this.idbfs.FS.rename(oldFile, newFile); await this.sync(); } async unlink(file: string) { this._unlinkRecursive(file); await this.sync(); } _createFolder(folder: string) { this.idbfs.FS.mkdirTree(folder, 0o777); } async createFolder(folder: string) { this._createFolder(folder); await this.sync(); } async sync() { await synchronizeIDBFS(this.idbfs, false); } readDir(dir: string): string[] { return this.idbfs.FS.readdir(dir); } stat(file: string) { return this.idbfs.FS.stat(file, false); } isFolder(file: string) { return (this.stat(file).mode & 0x4000) != 0; } async delete() { this.readDir("/root") // .filter(filterPseudoDir) // .forEach((e) => { try { this._unlinkRecursive(e); } catch (_) {} }); await this.sync(); try { await deleteDatabase("/root"); } catch (e) {} } } export async function setupFilesystem( progressHandler: ProgressHandler, downloadProgressHandler: DownloadPercentHandler, ) { const idbfs = await initializeIDBFS(); const fs = new Filesystem(idbfs); if (idbfs.FS.analyzePath("/root/api-set.bin", false).exists) { return fs; } const filesystem = await fetchFilesystem( progressHandler, downloadProgressHandler, ); filesystem.forEach((e) => { if (idbfs.FS.analyzePath("/" + e.name, false).exists) { return; } if (e.name.endsWith("/")) { idbfs.FS.mkdir("/" + e.name.slice(0, -1)); } else { const buffer = new Uint8Array(e.data); idbfs.FS.writeFile("/" + e.name, buffer); } }); await fs.sync(); return fs; }