Files
drop/server/server/internal/library/providers/filesystem.ts
T
DecDuck 796abf478f Fix GitHub Actions build (#427)
* Fix server build

* Remove server drop-base submod

* Update lockfile

* Use debian images for build

* Fix pino errors, lint

* Fix macOS keychain lookup
2026-06-21 10:37:54 +10:00

109 lines
3.1 KiB
TypeScript

import { ArkErrors, type } from "arktype";
import {
GameNotFoundError,
VersionNotFoundError,
type LibraryProvider,
} 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 FilesystemProviderConfig = type({
baseDir: "string",
});
export class FilesystemProvider implements LibraryProvider<
typeof FilesystemProviderConfig.infer
> {
private config: typeof FilesystemProviderConfig.infer;
private myId: string;
constructor(rawConfig: unknown, id: string) {
const config = FilesystemProviderConfig(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.";
}
id(): string {
return this.myId;
}
type(): LibraryBackend {
return LibraryBackend.Filesystem;
}
async listGames(): Promise<string[]> {
const dirs = fs.readdirSync(this.config.baseDir);
const folderDirs = dirs.filter((e) => {
const fullDir = path.join(this.config.baseDir, e);
return fs.lstatSync(fullDir).isDirectory();
});
return folderDirs;
}
async listVersions(
game: string,
ignoredVersions?: string[],
): Promise<string[]> {
const gameDir = path.join(this.config.baseDir, game);
if (!fs.existsSync(gameDir)) throw new GameNotFoundError();
const versionDirs = fs.readdirSync(gameDir);
const validVersionDirs = [];
for (const versionDir of versionDirs) {
if (ignoredVersions && ignoredVersions.includes(versionDir)) continue;
const fullDir = path.join(this.config.baseDir, game, versionDir);
const valid = await dropletInterface.hasBackend(fullDir);
if (!valid) continue;
validVersionDirs.push(versionDir);
}
return validVersionDirs;
}
async versionReaddir(game: string, version: string): Promise<string[]> {
const versionDir = path.join(this.config.baseDir, game, version);
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,
): Promise<string> {
const versionDir = path.join(this.config.baseDir, game, version);
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, version);
if (!fs.existsSync(filepath)) return undefined;
const stat = await dropletInterface.peekFile(filepath, filename);
return { size: Number(stat) };
}
fsStats() {
return fsStats(this.config.baseDir);
}
}