aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/controllers/ConfigController.ts
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-26 19:17:23 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-26 19:17:23 +0100
commite56cdad02c00adf3b779d9de62d460e78be204a6 (patch)
tree638d8ac94380b3d675f13c6eabc1e5ee51126342 /packages/main/src/controllers/ConfigController.ts
parentrefactor: Config persistence architecture (diff)
downloadsophie-e56cdad02c00adf3b779d9de62d460e78be204a6.tar.gz
sophie-e56cdad02c00adf3b779d9de62d460e78be204a6.tar.zst
sophie-e56cdad02c00adf3b779d9de62d460e78be204a6.zip
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)
Diffstat (limited to 'packages/main/src/controllers/ConfigController.ts')
-rw-r--r--packages/main/src/controllers/ConfigController.ts124
1 files changed, 124 insertions, 0 deletions
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 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { debounce } from 'lodash';
22import {
23 applySnapshot,
24 getSnapshot,
25 IDisposer,
26 onSnapshot,
27} from 'mobx-state-tree';
28import ms from 'ms';
29
30import { ConfigPersistenceService } from '../services/ConfigPersistenceService';
31import { Config, ConfigSnapshotOut } from '../stores/Config';
32
33const DEFAULT_DEBOUNCE_TIME = ms('1s');
34
35class ConfigController {
36 readonly #config: Config;
37
38 readonly #persistenceService: ConfigPersistenceService;
39
40 readonly #onSnapshotDisposer: IDisposer;
41
42 readonly #watcherDisposer: IDisposer;
43
44 #lastSnapshotOnDisk: ConfigSnapshotOut | null = null;
45
46 #writingConfig: boolean = false;
47
48 #configMTime: Date | null = null;
49
50 constructor(
51 config: Config,
52 persistenceService: ConfigPersistenceService,
53 debounceTime: number,
54 ) {
55 this.#config = config;
56 this.#persistenceService = persistenceService;
57 this.#onSnapshotDisposer = onSnapshot(this.#config, debounce((snapshot) => {
58 // We can compare snapshots by reference, since it is only recreated on store changes.
59 if (this.#lastSnapshotOnDisk !== snapshot) {
60 this.#writeConfig().catch((err) => {
61 console.log('Failed to write config on config change', err);
62 })
63 }
64 }, debounceTime));
65 this.#watcherDisposer = this.#persistenceService.watchConfig(async (mtime) => {
66 if (!this.#writingConfig && (this.#configMTime === null || mtime > this.#configMTime)) {
67 await this.#readConfig();
68 }
69 }, debounceTime);
70 }
71
72 async #readConfig(): Promise<boolean> {
73 const result = await this.#persistenceService.readConfig();
74 if (result.found) {
75 try {
76 applySnapshot(this.#config, result.data);
77 this.#lastSnapshotOnDisk = getSnapshot(this.#config);
78 console.log('Loaded config');
79 } catch (err) {
80 console.error('Failed to read config', result.data, err);
81 }
82 }
83 return result.found;
84 }
85
86 async #writeConfig(): Promise<void> {
87 const snapshot = getSnapshot(this.#config);
88 this.#writingConfig = true;
89 try {
90 this.#configMTime = await this.#persistenceService.writeConfig(snapshot);
91 this.#lastSnapshotOnDisk = snapshot;
92 console.log('Wrote config');
93 } finally {
94 this.#writingConfig = false;
95 }
96 }
97
98 async initConfig(): Promise<void> {
99 const foundConfig: boolean = await this.#readConfig();
100 if (!foundConfig) {
101 console.log('Creating new config file');
102 try {
103 await this.#writeConfig();
104 } catch (err) {
105 console.error('Failed to initialize config');
106 }
107 }
108 }
109
110 dispose(): void {
111 this.#onSnapshotDisposer();
112 this.#watcherDisposer();
113 }
114}
115
116export async function initConfig(
117 config: Config,
118 persistenceService: ConfigPersistenceService,
119 debounceTime: number = DEFAULT_DEBOUNCE_TIME,
120): Promise<IDisposer> {
121 const controller = new ConfigController(config, persistenceService, debounceTime);
122 await controller.initConfig();
123 return () => controller.dispose();
124}