import { ArkErrors, type } from "arktype"; import type { LibraryProvider } from "../provider"; import { VersionNotFoundError } from "../provider"; import { LibraryBackend } from "~/prisma/client/enums"; import fs from "fs"; import path from "path"; import { fsStats } from "~/server/internal/utils/files"; import { dropletInterface } from "../../services/torrential/droplet-interface"; export const FlatFilesystemProviderConfig = type({ baseDir: "string", }); export class FlatFilesystemProvider implements LibraryProvider { private config: typeof FlatFilesystemProviderConfig.infer; private myId: string; constructor(rawConfig: unknown, id: string) { const config = FlatFilesystemProviderConfig(rawConfig); if (config instanceof ArkErrors) { throw new Error( `Failed to create filesystem provider: ${config.summary}`, ); } this.myId = id; this.config = config; if (!fs.existsSync(this.config.baseDir)) throw "Base directory does not exist."; } type() { return LibraryBackend.FlatFilesystem; } id() { return this.myId; } /** * These are basically our versions, but also our games. * @returns list of valid games */ async listGames() { const versionDirs = fs.readdirSync(this.config.baseDir); const validVersionDirs = []; for (const versionDir of versionDirs) { const fullDir = path.join(this.config.baseDir, versionDir); const valid = await dropletInterface.hasBackend(fullDir); if (!valid) continue; validVersionDirs.push(versionDir); } return validVersionDirs; } /** * Doesn't do anything, just returns "default" * @param _game Ignored * @returns */ async listVersions(_game: string) { return ["default"]; } async versionReaddir(game: string, _version: string) { const versionDir = path.join(this.config.baseDir, game); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); return await dropletInterface.listFiles(versionDir); } async generateDropletManifest( game: string, _version: string, progress: (v: number) => void, log: (v: string) => void, ) { const versionDir = path.join(this.config.baseDir, game); if (!fs.existsSync(versionDir)) throw new VersionNotFoundError(); const manifest = await dropletInterface.generateDropletManifest( versionDir, progress, log, ); return manifest; } async peekFile(game: string, _version: string, filename: string) { const filepath = path.join(this.config.baseDir, game); if (!fs.existsSync(filepath)) return undefined; const stat = await dropletInterface.peekFile(filepath, filename); return { size: Number(stat) }; } fsStats() { return fsStats(this.config.baseDir); } }