aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/controllers')
-rw-r--r--packages/main/src/controllers/ConfigController.ts105
-rw-r--r--packages/main/src/controllers/MainController.ts38
-rw-r--r--packages/main/src/controllers/NativeThemeController.ts37
3 files changed, 106 insertions, 74 deletions
diff --git a/packages/main/src/controllers/ConfigController.ts b/packages/main/src/controllers/ConfigController.ts
index 6690548..a28746c 100644
--- a/packages/main/src/controllers/ConfigController.ts
+++ b/packages/main/src/controllers/ConfigController.ts
@@ -25,56 +25,62 @@ import {
25 IDisposer, 25 IDisposer,
26 onSnapshot, 26 onSnapshot,
27} from 'mobx-state-tree'; 27} from 'mobx-state-tree';
28import ms from 'ms';
29 28
30import { ConfigPersistenceService } from '../services/ConfigPersistenceService'; 29import type { ConfigPersistenceService } from '../services/ConfigPersistenceService';
31import { Config, ConfigSnapshotOut } from '../stores/Config'; 30import type { Config, ConfigSnapshotOut } from '../stores/Config';
32 31
33const DEFAULT_DEBOUNCE_TIME = ms('1s'); 32export class ConfigController {
33 static inject = ['configPersistenceService', 'configDebounceTime'] as const;
34 34
35class ConfigController { 35 private config: Config | null = null;
36 readonly #config: Config;
37 36
38 readonly #persistenceService: ConfigPersistenceService; 37 private onSnapshotDisposer: IDisposer | null = null;
39 38
40 readonly #onSnapshotDisposer: IDisposer; 39 private lastSnapshotOnDisk: ConfigSnapshotOut | null = null;
41 40
42 readonly #watcherDisposer: IDisposer; 41 private writingConfig: boolean = false;
43 42
44 #lastSnapshotOnDisk: ConfigSnapshotOut | null = null; 43 constructor(
44 private readonly persistenceService: ConfigPersistenceService,
45 private readonly debounceTime: number,
46 ) {
47 }
45 48
46 #writingConfig: boolean = false; 49 async initConfig(config: Config): Promise<void> {
50 this.config = config;
47 51
48 #configMTime: Date | null = null; 52 const foundConfig: boolean = await this.readConfig();
53 if (!foundConfig) {
54 console.log('Creating new config file');
55 try {
56 await this.writeConfig();
57 } catch (err) {
58 console.error('Failed to initialize config');
59 }
60 }
49 61
50 constructor( 62 this.onSnapshotDisposer = onSnapshot(this.config, debounce((snapshot) => {
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. 63 // We can compare snapshots by reference, since it is only recreated on store changes.
59 if (this.#lastSnapshotOnDisk !== snapshot) { 64 if (this.lastSnapshotOnDisk !== snapshot) {
60 this.#writeConfig().catch((err) => { 65 this.writeConfig().catch((err) => {
61 console.log('Failed to write config on config change', err); 66 console.log('Failed to write config on config change', err);
62 }) 67 })
63 } 68 }
64 }, debounceTime)); 69 }, this.debounceTime));
65 this.#watcherDisposer = this.#persistenceService.watchConfig(async (mtime) => { 70
66 if (!this.#writingConfig && (this.#configMTime === null || mtime > this.#configMTime)) { 71 this.persistenceService.watchConfig(async () => {
67 await this.#readConfig(); 72 if (!this.writingConfig) {
73 await this.readConfig();
68 } 74 }
69 }, debounceTime); 75 }, this.debounceTime);
70 } 76 }
71 77
72 async #readConfig(): Promise<boolean> { 78 private async readConfig(): Promise<boolean> {
73 const result = await this.#persistenceService.readConfig(); 79 const result = await this.persistenceService.readConfig();
74 if (result.found) { 80 if (result.found) {
75 try { 81 try {
76 applySnapshot(this.#config, result.data); 82 applySnapshot(this.config!, result.data);
77 this.#lastSnapshotOnDisk = getSnapshot(this.#config); 83 this.lastSnapshotOnDisk = getSnapshot(this.config!);
78 console.log('Loaded config'); 84 console.log('Loaded config');
79 } catch (err) { 85 } catch (err) {
80 console.error('Failed to read config', result.data, err); 86 console.error('Failed to read config', result.data, err);
@@ -83,42 +89,19 @@ class ConfigController {
83 return result.found; 89 return result.found;
84 } 90 }
85 91
86 async #writeConfig(): Promise<void> { 92 private async writeConfig(): Promise<void> {
87 const snapshot = getSnapshot(this.#config); 93 const snapshot = getSnapshot(this.config!);
88 this.#writingConfig = true; 94 this.writingConfig = true;
89 try { 95 try {
90 this.#configMTime = await this.#persistenceService.writeConfig(snapshot); 96 await this.persistenceService.writeConfig(snapshot);
91 this.#lastSnapshotOnDisk = snapshot; 97 this.lastSnapshotOnDisk = snapshot;
92 console.log('Wrote config'); 98 console.log('Wrote config');
93 } finally { 99 } finally {
94 this.#writingConfig = false; 100 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 } 101 }
108 } 102 }
109 103
110 dispose(): void { 104 dispose(): void {
111 this.#onSnapshotDisposer(); 105 this.onSnapshotDisposer?.();
112 this.#watcherDisposer();
113 } 106 }
114} 107}
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}
diff --git a/packages/main/src/controllers/MainController.ts b/packages/main/src/controllers/MainController.ts
new file mode 100644
index 0000000..6b97330
--- /dev/null
+++ b/packages/main/src/controllers/MainController.ts
@@ -0,0 +1,38 @@
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 type { ConfigController } from './ConfigController';
22import type { NativeThemeController } from './NativeThemeController';
23import type { MainStore } from '../stores/MainStore';
24
25export class MainController {
26 static inject = ['configController', 'nativeThemeController'] as const;
27
28 constructor(
29 private readonly configController: ConfigController,
30 private readonly nativeThemeController: NativeThemeController,
31 ) {
32 }
33
34 async connect(store: MainStore): Promise<void> {
35 await this.configController.initConfig(store.config);
36 this.nativeThemeController.connect(store);
37 }
38}
diff --git a/packages/main/src/controllers/NativeThemeController.ts b/packages/main/src/controllers/NativeThemeController.ts
index 07a3292..a50d41e 100644
--- a/packages/main/src/controllers/NativeThemeController.ts
+++ b/packages/main/src/controllers/NativeThemeController.ts
@@ -18,21 +18,32 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { nativeTheme } from 'electron';
21import { autorun } from 'mobx'; 22import { autorun } from 'mobx';
22import type { IDisposer } from 'mobx-state-tree'; 23import { IDisposer } from 'mobx-state-tree';
23 24
24import type { NativeThemeService } from '../services/NativeThemeService';
25import type { MainStore } from '../stores/MainStore'; 25import type { MainStore } from '../stores/MainStore';
26 26
27export function initNativeTheme(store: MainStore, service: NativeThemeService): IDisposer { 27export class NativeThemeController {
28 const themeSourceReactionDisposer = autorun(() => { 28 private autorunDisposer: IDisposer | null = null;
29 service.setThemeSource(store.config.themeSource); 29
30 }); 30 private shouldUseDarkColorsListener: (() => void) | null = null;
31 const onShouldUseDarkColorsUpdatedDisposer = service.onShouldUseDarkColorsUpdated( 31
32 store.setShouldUseDarkColors, 32 connect(store: MainStore): void {
33 ); 33 this.autorunDisposer = autorun(() => {
34 return () => { 34 nativeTheme.themeSource = store.config.themeSource;
35 onShouldUseDarkColorsUpdatedDisposer(); 35 });
36 themeSourceReactionDisposer(); 36 store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors);
37 }; 37 this.shouldUseDarkColorsListener = () => {
38 store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors);
39 };
40 nativeTheme.on('updated', this.shouldUseDarkColorsListener);
41 }
42
43 dispose(): void {
44 if (this.shouldUseDarkColorsListener !== null) {
45 nativeTheme.off('updated', this.shouldUseDarkColorsListener);
46 }
47 this.autorunDisposer?.();
48 }
38} 49}