From e48b3d9b564572a07a71735ec920143ebd9a0188 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 18:09:29 +0200 Subject: [PATCH] Add write-cache to desktop --- apps/desktop/src/main.ts | 3 + .../services/electron-storage.service.ts | 60 +++++++++++++++---- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 20c632ec4ac..413db194ad3 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -121,6 +121,9 @@ export class Main { const storageDefaults: any = {}; this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults); + app.on("before-quit", () => { + this.storageService.flush(); + }); this.memoryStorageService = new MemoryStorageService(); this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders(); const storageServiceProvider = new StorageServiceProvider( diff --git a/apps/desktop/src/platform/services/electron-storage.service.ts b/apps/desktop/src/platform/services/electron-storage.service.ts index 58585df700a..9a7623995c6 100644 --- a/apps/desktop/src/platform/services/electron-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-storage.service.ts @@ -24,7 +24,7 @@ const Store: ElectronStoreConstructor = require("electron-store"); interface ElectronStore { get: (key: string) => unknown; - set: (key: string, obj: unknown) => void; + set: (obj: unknown) => void; delete: (key: string) => void; } @@ -39,6 +39,44 @@ interface SaveOptions extends BaseOptions<"save"> { type Options = BaseOptions<"get"> | BaseOptions<"has"> | SaveOptions | BaseOptions<"remove">; +// Max one second +const MAX_FILE_CACHE_WRITE_INTERVAL = 1000; +class InMemoryFileCache { + private fileCache: any = null; + private needsWrite = false; + private lastWritten = 0; + + constructor(private store: ElectronStore) { + this.fileCache = (this.store as any).store; + setInterval(() => { + if (this.needsWrite && Date.now() - this.lastWritten > MAX_FILE_CACHE_WRITE_INTERVAL) { + this.needsWrite = false; + this.store.set(this.fileCache); + this.lastWritten = Date.now(); + } + }, MAX_FILE_CACHE_WRITE_INTERVAL); + } + + get(key: string): unknown { + return this.fileCache[key]; + } + + set(key: string, obj: unknown): void { + this.fileCache[key] = obj; + this.needsWrite = true; + } + + delete(key: string): void { + delete this.fileCache[key]; + this.needsWrite = true; + } + + flush(): void { + this.store.set(this.fileCache); + this.needsWrite = false; + } +} + export class ElectronStorageService implements AbstractStorageService { private store: ElectronStore; private updatesSubject = new Subject(); @@ -48,7 +86,7 @@ export class ElectronStorageService implements AbstractStorageService { // // electron store and conf read the entire file per individual key accessed, which blocks the main // thread making in-memory store access slow, and causing a lot of file I/O. - private fileCache: any = null; + private fileCache: InMemoryFileCache; constructor(dir: string, defaults = {}) { if (!fs.existsSync(dir)) { @@ -60,7 +98,7 @@ export class ElectronStorageService implements AbstractStorageService { }; this.store = new Store(storeConfig); this.updates$ = this.updatesSubject.asObservable(); - this.fileCache = (this.store as any).store; + this.fileCache = new InMemoryFileCache(this.store); ipcMain.handle("storageService", (event, options: Options) => { switch (options.action) { @@ -81,11 +119,11 @@ export class ElectronStorageService implements AbstractStorageService { } get(key: string): Promise { - return Promise.resolve(this.fileCache[key]); + return Promise.resolve(this.fileCache.get(key) as T); } has(key: string): Promise { - return Promise.resolve(this.fileCache[key] !== undefined); + return Promise.resolve(this.fileCache.get(key) !== undefined); } save(key: string, obj: unknown): Promise { @@ -97,18 +135,18 @@ export class ElectronStorageService implements AbstractStorageService { obj = Array.from(obj); } - this.fileCache[key] = obj; - this.store.set(key, obj); - + this.fileCache.set(key, obj); this.updatesSubject.next({ key, updateType: "save" }); return Promise.resolve(); } remove(key: string): Promise { - delete this.fileCache[key]; - this.store.delete(key); - + this.fileCache.delete(key); this.updatesSubject.next({ key, updateType: "remove" }); return Promise.resolve(); } + + flush() { + this.fileCache.flush(); + } }