From e56cdad02c00adf3b779d9de62d460e78be204a6 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 26 Dec 2021 19:17:23 +0100 Subject: refactor: Clarify main process architecture * stores: reactive data structures to hold application state * controllers: subscribe to store changes and call store actions in response to external events from services * services: integrate with the nodejs and electron environment (should be mocked for unit testing) --- packages/main/src/controllers/ConfigController.ts | 124 ++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 packages/main/src/controllers/ConfigController.ts (limited to 'packages/main/src/controllers/ConfigController.ts') diff --git a/packages/main/src/controllers/ConfigController.ts b/packages/main/src/controllers/ConfigController.ts new file mode 100644 index 0000000..6690548 --- /dev/null +++ b/packages/main/src/controllers/ConfigController.ts @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021-2022 Kristóf Marussy + * + * This file is part of Sophie. + * + * Sophie is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { debounce } from 'lodash'; +import { + applySnapshot, + getSnapshot, + IDisposer, + onSnapshot, +} from 'mobx-state-tree'; +import ms from 'ms'; + +import { ConfigPersistenceService } from '../services/ConfigPersistenceService'; +import { Config, ConfigSnapshotOut } from '../stores/Config'; + +const DEFAULT_DEBOUNCE_TIME = ms('1s'); + +class ConfigController { + readonly #config: Config; + + readonly #persistenceService: ConfigPersistenceService; + + readonly #onSnapshotDisposer: IDisposer; + + readonly #watcherDisposer: IDisposer; + + #lastSnapshotOnDisk: ConfigSnapshotOut | null = null; + + #writingConfig: boolean = false; + + #configMTime: Date | null = null; + + constructor( + config: Config, + persistenceService: ConfigPersistenceService, + debounceTime: number, + ) { + this.#config = config; + this.#persistenceService = persistenceService; + this.#onSnapshotDisposer = onSnapshot(this.#config, debounce((snapshot) => { + // We can compare snapshots by reference, since it is only recreated on store changes. + if (this.#lastSnapshotOnDisk !== snapshot) { + this.#writeConfig().catch((err) => { + console.log('Failed to write config on config change', err); + }) + } + }, debounceTime)); + this.#watcherDisposer = this.#persistenceService.watchConfig(async (mtime) => { + if (!this.#writingConfig && (this.#configMTime === null || mtime > this.#configMTime)) { + await this.#readConfig(); + } + }, debounceTime); + } + + async #readConfig(): Promise { + const result = await this.#persistenceService.readConfig(); + if (result.found) { + try { + applySnapshot(this.#config, result.data); + this.#lastSnapshotOnDisk = getSnapshot(this.#config); + console.log('Loaded config'); + } catch (err) { + console.error('Failed to read config', result.data, err); + } + } + return result.found; + } + + async #writeConfig(): Promise { + const snapshot = getSnapshot(this.#config); + this.#writingConfig = true; + try { + this.#configMTime = await this.#persistenceService.writeConfig(snapshot); + this.#lastSnapshotOnDisk = snapshot; + console.log('Wrote config'); + } finally { + this.#writingConfig = false; + } + } + + async initConfig(): Promise { + const foundConfig: boolean = await this.#readConfig(); + if (!foundConfig) { + console.log('Creating new config file'); + try { + await this.#writeConfig(); + } catch (err) { + console.error('Failed to initialize config'); + } + } + } + + dispose(): void { + this.#onSnapshotDisposer(); + this.#watcherDisposer(); + } +} + +export async function initConfig( + config: Config, + persistenceService: ConfigPersistenceService, + debounceTime: number = DEFAULT_DEBOUNCE_TIME, +): Promise { + const controller = new ConfigController(config, persistenceService, debounceTime); + await controller.initConfig(); + return () => controller.dispose(); +} -- cgit v1.2.3-54-g00ecf