1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-16 11:41:50 +00:00
tobspr_shapez.io/electron/src/fsjob.ts
Даниїл Григор'єв ae1cd71d3b
Fix file writes to non-existent directories
When the game attempts to create the settings file but its directory
does not exist yet, an early error occurs. In reality, there is no way
for the directory to be created without user intervention. This change
makes sure the directory is created before any attempt to write a file
is done. Errors related to directory creation and/or file writes are
still reported as usual.
2025-03-26 22:46:14 +02:00

75 lines
2.2 KiB
TypeScript

import fs from "fs/promises";
import path from "path";
import { userData } from "./config.js";
interface GenericFsJob {
filename: string;
}
type ListFsJob = GenericFsJob & { type: "list" };
type ReadFsJob = GenericFsJob & { type: "read" };
type WriteFsJob = GenericFsJob & { type: "write"; contents: string };
type DeleteFsJob = GenericFsJob & { type: "delete" };
export type FsJob = ListFsJob | ReadFsJob | WriteFsJob | DeleteFsJob;
type FsJobResult = string | string[] | void;
export class FsJobHandler {
readonly rootDir: string;
constructor(subDir: string) {
this.rootDir = path.join(userData, subDir);
}
handleJob(job: FsJob): Promise<FsJobResult> {
const filename = this.safeFileName(job.filename);
switch (job.type) {
case "list":
return this.list(filename);
case "read":
return this.read(filename);
case "write":
return this.write(filename, job.contents);
case "delete":
return this.delete(filename);
}
// @ts-expect-error this method can actually receive garbage
throw new Error(`Unknown FS job type: ${job.type}`);
}
private list(subdir: string): Promise<string[]> {
// Bare-bones implementation
return fs.readdir(subdir);
}
private read(file: string): Promise<string> {
return fs.readFile(file, "utf-8");
}
private async write(file: string, contents: string): Promise<string> {
// The target directory might not exist, ensure it does
const parentDir = path.dirname(file);
await fs.mkdir(parentDir, { recursive: true });
// Backups not implemented yet.
await fs.writeFile(file, contents, {
encoding: "utf-8",
flush: true,
});
return contents;
}
private delete(file: string): Promise<void> {
return fs.unlink(file);
}
private safeFileName(name: string) {
// TODO: Rather than restricting file names, attempt to resolve everything
// relative to the data directory (i.e. normalize the file path, then join)
const relative = name.replace(/[^a-z.0-9_-]/gi, "_");
return path.join(this.rootDir, relative);
}
}