mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-11 09:11:50 +00:00
Remove main process compression
To be simplified and replaced with renderer-side web workers.
This commit is contained in:
parent
3abfa9c35d
commit
efb9eee286
@ -2,7 +2,6 @@ import { BrowserWindow, dialog, FileFilter } from "electron";
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { userData } from "./config.js";
|
import { userData } from "./config.js";
|
||||||
import { StorageInterface } from "./storage/interface.js";
|
|
||||||
|
|
||||||
interface GenericFsJob {
|
interface GenericFsJob {
|
||||||
id: string;
|
id: string;
|
||||||
@ -11,30 +10,28 @@ interface GenericFsJob {
|
|||||||
export type InitializeFsJob = GenericFsJob & { type: "initialize" };
|
export type InitializeFsJob = GenericFsJob & { type: "initialize" };
|
||||||
type ListFsJob = GenericFsJob & { type: "list"; filename: string };
|
type ListFsJob = GenericFsJob & { type: "list"; filename: string };
|
||||||
type ReadFsJob = GenericFsJob & { type: "read"; filename: string };
|
type ReadFsJob = GenericFsJob & { type: "read"; filename: string };
|
||||||
type WriteFsJob<T> = GenericFsJob & { type: "write"; filename: string; contents: T };
|
type WriteFsJob = GenericFsJob & { type: "write"; filename: string; contents: Uint8Array };
|
||||||
type DeleteFsJob = GenericFsJob & { type: "delete"; filename: string };
|
type DeleteFsJob = GenericFsJob & { type: "delete"; filename: string };
|
||||||
|
|
||||||
type OpenExternalFsJob = GenericFsJob & { type: "open-external"; extension: string };
|
type OpenExternalFsJob = GenericFsJob & { type: "open-external"; extension: string };
|
||||||
type SaveExternalFsJob<T> = GenericFsJob & { type: "save-external"; filename: string; contents: T };
|
type SaveExternalFsJob = GenericFsJob & { type: "save-external"; filename: string; contents: Uint8Array };
|
||||||
|
|
||||||
export type FsJob<T> =
|
export type FsJob =
|
||||||
| InitializeFsJob
|
| InitializeFsJob
|
||||||
| ListFsJob
|
| ListFsJob
|
||||||
| ReadFsJob
|
| ReadFsJob
|
||||||
| WriteFsJob<T>
|
| WriteFsJob
|
||||||
| DeleteFsJob
|
| DeleteFsJob
|
||||||
| OpenExternalFsJob
|
| OpenExternalFsJob
|
||||||
| SaveExternalFsJob<T>;
|
| SaveExternalFsJob;
|
||||||
type FsJobResult<T> = T | string[] | void;
|
type FsJobResult = Uint8Array | string[] | void;
|
||||||
|
|
||||||
export class FsJobHandler<T> {
|
export class FsJobHandler {
|
||||||
readonly rootDir: string;
|
readonly rootDir: string;
|
||||||
private readonly storage: StorageInterface<T>;
|
|
||||||
private initialized = false;
|
private initialized = false;
|
||||||
|
|
||||||
constructor(subDir: string, storage: StorageInterface<T>) {
|
constructor(subDir: string) {
|
||||||
this.rootDir = path.join(userData, subDir);
|
this.rootDir = path.join(userData, subDir);
|
||||||
this.storage = storage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
@ -47,7 +44,7 @@ export class FsJobHandler<T> {
|
|||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleJob(job: FsJob<T>): Promise<FsJobResult<T>> {
|
handleJob(job: FsJob): Promise<FsJobResult> {
|
||||||
switch (job.type) {
|
switch (job.type) {
|
||||||
case "initialize":
|
case "initialize":
|
||||||
return this.initialize();
|
return this.initialize();
|
||||||
@ -63,18 +60,18 @@ export class FsJobHandler<T> {
|
|||||||
case "list":
|
case "list":
|
||||||
return this.list(filename);
|
return this.list(filename);
|
||||||
case "read":
|
case "read":
|
||||||
return this.storage.read(filename);
|
return fs.readFile(filename);
|
||||||
case "write":
|
case "write":
|
||||||
return this.write(filename, job.contents);
|
return this.write(filename, job.contents);
|
||||||
case "delete":
|
case "delete":
|
||||||
return this.storage.delete(filename);
|
return fs.unlink(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error this method can actually receive garbage
|
// @ts-expect-error this method can actually receive garbage
|
||||||
throw new Error(`Unknown FS job type: ${job.type}`);
|
throw new Error(`Unknown FS job type: ${job.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openExternal(extension: string): Promise<T | undefined> {
|
private async openExternal(extension: string): Promise<Uint8Array | undefined> {
|
||||||
const filters = this.getFileDialogFilters(extension === "*" ? undefined : extension);
|
const filters = this.getFileDialogFilters(extension === "*" ? undefined : extension);
|
||||||
const window = BrowserWindow.getAllWindows()[0]!;
|
const window = BrowserWindow.getAllWindows()[0]!;
|
||||||
|
|
||||||
@ -83,10 +80,10 @@ export class FsJobHandler<T> {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.storage.read(result.filePaths[0]);
|
return await fs.readFile(result.filePaths[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveExternal(filename: string, contents: T): Promise<void> {
|
private async saveExternal(filename: string, contents: Uint8Array): Promise<void> {
|
||||||
// Try to guess extension
|
// Try to guess extension
|
||||||
const ext = filename.indexOf(".") < 1 ? filename.split(".").at(-1)! : undefined;
|
const ext = filename.indexOf(".") < 1 ? filename.split(".").at(-1)! : undefined;
|
||||||
const filters = this.getFileDialogFilters(ext);
|
const filters = this.getFileDialogFilters(ext);
|
||||||
@ -97,7 +94,7 @@ export class FsJobHandler<T> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.storage.write(result.filePath, contents);
|
return await fs.writeFile(result.filePath, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFileDialogFilters(extension?: string): FileFilter[] {
|
private getFileDialogFilters(extension?: string): FileFilter[] {
|
||||||
@ -118,12 +115,13 @@ export class FsJobHandler<T> {
|
|||||||
return fs.readdir(subdir);
|
return fs.readdir(subdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async write(file: string, contents: T): Promise<void> {
|
private async write(file: string, contents: Uint8Array): Promise<void> {
|
||||||
// The target directory might not exist, ensure it does
|
// The target directory might not exist, ensure it does
|
||||||
const parentDir = path.dirname(file);
|
const parentDir = path.dirname(file);
|
||||||
await fs.mkdir(parentDir, { recursive: true });
|
await fs.mkdir(parentDir, { recursive: true });
|
||||||
|
|
||||||
await this.storage.write(file, contents);
|
console.log(contents);
|
||||||
|
await fs.writeFile(file, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
private safeFileName(name: string) {
|
private safeFileName(name: string) {
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { BrowserWindow, IpcMainInvokeEvent, ipcMain } from "electron";
|
import { BrowserWindow, IpcMainInvokeEvent, ipcMain } from "electron";
|
||||||
import { FsJob, FsJobHandler } from "./fsjob.js";
|
import { FsJob, FsJobHandler } from "./fsjob.js";
|
||||||
import { ModLoader } from "./mods/loader.js";
|
import { ModLoader } from "./mods/loader.js";
|
||||||
import { SavesStorage } from "./storage/saves.js";
|
|
||||||
|
|
||||||
export class IpcHandler {
|
export class IpcHandler {
|
||||||
private readonly savesHandler = new FsJobHandler("saves", new SavesStorage());
|
private readonly savesHandler = new FsJobHandler("saves");
|
||||||
private readonly modLoader: ModLoader;
|
private readonly modLoader: ModLoader;
|
||||||
|
|
||||||
constructor(modLoader: ModLoader) {
|
constructor(modLoader: ModLoader) {
|
||||||
@ -20,7 +19,7 @@ export class IpcHandler {
|
|||||||
// ipcMain.handle("open-mods-folder", ...)
|
// ipcMain.handle("open-mods-folder", ...)
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleFsJob(_event: IpcMainInvokeEvent, job: FsJob<unknown>) {
|
private handleFsJob(_event: IpcMainInvokeEvent, job: FsJob) {
|
||||||
if (job.id !== "saves") {
|
if (job.id !== "saves") {
|
||||||
throw new Error("Storages other than saves/ are not implemented yet");
|
throw new Error("Storages other than saves/ are not implemented yet");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
export interface StorageInterface<T> {
|
|
||||||
read(file: string): Promise<T>;
|
|
||||||
write(file: string, contents: T): Promise<void>;
|
|
||||||
delete(file: string): Promise<void>;
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import fs from "node:fs/promises";
|
|
||||||
import { StorageInterface } from "./interface.js";
|
|
||||||
|
|
||||||
export class RawStorage implements StorageInterface<string> {
|
|
||||||
read(file: string): Promise<string> {
|
|
||||||
return fs.readFile(file, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
write(file: string, contents: string): Promise<void> {
|
|
||||||
return fs.writeFile(file, contents, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(file: string): Promise<void> {
|
|
||||||
return fs.unlink(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import { decodeAsync, encode } from "@msgpack/msgpack";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import { pipeline } from "node:stream/promises";
|
|
||||||
import { createGunzip, createGzip } from "node:zlib";
|
|
||||||
import { StorageInterface } from "./interface.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This storage implementation is used for savegame files and other
|
|
||||||
* ReadWriteProxy objects. It uses gzipped MessagePack as the file format.
|
|
||||||
*/
|
|
||||||
export class SavesStorage implements StorageInterface<unknown> {
|
|
||||||
async read(file: string): Promise<unknown> {
|
|
||||||
const stream = fs.createReadStream(file);
|
|
||||||
const gunzip = createGunzip();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Any filesystem errors will be uncovered here. This code ensures we return the most
|
|
||||||
// relevant rejection, or resolve with the decoded data
|
|
||||||
const [readResult, decodeResult] = await Promise.allSettled([
|
|
||||||
pipeline(stream, gunzip),
|
|
||||||
decodeAsync(gunzip),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (decodeResult.status === "fulfilled") {
|
|
||||||
return decodeResult.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the most relevant error
|
|
||||||
throw readResult.status === "rejected" ? readResult.reason : decodeResult.reason;
|
|
||||||
} finally {
|
|
||||||
stream.close();
|
|
||||||
gunzip.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async write(file: string, contents: unknown): Promise<void> {
|
|
||||||
const stream = fs.createWriteStream(file);
|
|
||||||
const gzip = createGzip();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const encoded = encode(contents);
|
|
||||||
const blob = new Blob([encoded]);
|
|
||||||
|
|
||||||
return await pipeline(blob.stream(), gzip, stream);
|
|
||||||
} finally {
|
|
||||||
gzip.close();
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(file: string): Promise<void> {
|
|
||||||
return fs.promises.unlink(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user