aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/services
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-27 19:41:46 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-27 19:51:14 +0100
commitf5f27eddc93314e8e10ab96c7bdb5c626142a1d3 (patch)
tree99ce5eca8c2bf3590612b24e33d4fe727bd240ab /packages/main/src/services
parentfix: Allow the shared store listener to re-register in dev mode (diff)
downloadsophie-f5f27eddc93314e8e10ab96c7bdb5c626142a1d3.tar.gz
sophie-f5f27eddc93314e8e10ab96c7bdb5c626142a1d3.tar.zst
sophie-f5f27eddc93314e8e10ab96c7bdb5c626142a1d3.zip
refactor: Inversion of control with typed-inject
Diffstat (limited to 'packages/main/src/services')
-rw-r--r--packages/main/src/services/ConfigPersistenceService.ts53
-rw-r--r--packages/main/src/services/NativeThemeService.ts38
2 files changed, 30 insertions, 61 deletions
diff --git a/packages/main/src/services/ConfigPersistenceService.ts b/packages/main/src/services/ConfigPersistenceService.ts
index 85b0088..61123d9 100644
--- a/packages/main/src/services/ConfigPersistenceService.ts
+++ b/packages/main/src/services/ConfigPersistenceService.ts
@@ -17,12 +17,10 @@
17 * 17 *
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20import { FSWatcher, watch } from 'fs';
21import { watch } from 'fs';
22import { readFile, stat, writeFile } from 'fs/promises'; 21import { readFile, stat, writeFile } from 'fs/promises';
23import JSON5 from 'json5'; 22import JSON5 from 'json5';
24import { throttle } from 'lodash'; 23import { throttle } from 'lodash';
25import { IDisposer } from 'mobx-state-tree';
26import { join } from 'path'; 24import { join } from 'path';
27 25
28import type { ConfigSnapshotOut } from '../stores/Config'; 26import type { ConfigSnapshotOut } from '../stores/Config';
@@ -30,25 +28,26 @@ import type { ConfigSnapshotOut } from '../stores/Config';
30export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; 28export type ReadConfigResult = { found: true; data: unknown; } | { found: false; };
31 29
32export class ConfigPersistenceService { 30export class ConfigPersistenceService {
33 readonly #userDataDir: string; 31 static inject = ['userDataDir', 'configFileName'] as const;
32
33 private readonly configFilePath: string;
34 34
35 readonly #configFileName: string; 35 private timeLastWritten: Date | null = null;
36 36
37 readonly #configFilePath: string; 37 private watcher: FSWatcher | null = null;
38 38
39 constructor( 39 constructor(
40 userDataDir: string, 40 private readonly userDataDir: string,
41 configFileName: string, 41 private readonly configFileName: string,
42 ) { 42 ) {
43 this.#userDataDir = userDataDir; 43 this.configFileName = configFileName;
44 this.#configFileName = configFileName; 44 this.configFilePath = join(this.userDataDir, this.configFileName);
45 this.#configFilePath = join(this.#userDataDir, this.#configFileName);
46 } 45 }
47 46
48 async readConfig(): Promise<ReadConfigResult> { 47 async readConfig(): Promise<ReadConfigResult> {
49 let configStr; 48 let configStr;
50 try { 49 try {
51 configStr = await readFile(this.#configFilePath, 'utf8'); 50 configStr = await readFile(this.configFilePath, 'utf8');
52 } catch (err) { 51 } catch (err) {
53 if ((err as NodeJS.ErrnoException).code === 'ENOENT') { 52 if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
54 return { found: false }; 53 return { found: false };
@@ -61,20 +60,24 @@ export class ConfigPersistenceService {
61 }; 60 };
62 } 61 }
63 62
64 async writeConfig(configSnapshot: ConfigSnapshotOut): Promise<Date> { 63 async writeConfig(configSnapshot: ConfigSnapshotOut): Promise<void> {
65 const configJson = JSON5.stringify(configSnapshot, { 64 const configJson = JSON5.stringify(configSnapshot, {
66 space: 2, 65 space: 2,
67 }); 66 });
68 await writeFile(this.#configFilePath, configJson, 'utf8'); 67 await writeFile(this.configFilePath, configJson, 'utf8');
69 const { mtime } = await stat(this.#configFilePath); 68 const stats = await stat(this.configFilePath);
70 return mtime; 69 this.timeLastWritten = stats.mtime;
71 } 70 }
72 71
73 watchConfig(callback: (mtime: Date) => Promise<void>, throttleMs: number): IDisposer { 72 watchConfig(callback: () => Promise<void>, throttleMs: number): void {
73 if (this.watcher !== null) {
74 throw new Error('watchConfig was already called');
75 }
76
74 const configChanged = throttle(async () => { 77 const configChanged = throttle(async () => {
75 let mtime: Date; 78 let mtime: Date;
76 try { 79 try {
77 const stats = await stat(this.#configFilePath); 80 const stats = await stat(this.configFilePath);
78 mtime = stats.mtime; 81 mtime = stats.mtime;
79 } catch (err) { 82 } catch (err) {
80 if ((err as NodeJS.ErrnoException).code === 'ENOENT') { 83 if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
@@ -82,22 +85,26 @@ export class ConfigPersistenceService {
82 } 85 }
83 throw err; 86 throw err;
84 } 87 }
85 return callback(mtime); 88 if (this.timeLastWritten === null || mtime > this.timeLastWritten) {
89 return callback();
90 }
86 }, throttleMs); 91 }, throttleMs);
87 92
88 const watcher = watch(this.#userDataDir, { 93 this.watcher = watch(this.userDataDir, {
89 persistent: false, 94 persistent: false,
90 }); 95 });
91 96
92 watcher.on('change', (eventType, filename) => { 97 this.watcher.on('change', (eventType, filename) => {
93 if (eventType === 'change' 98 if (eventType === 'change'
94 && (filename === this.#configFileName || filename === null)) { 99 && (filename === this.configFileName || filename === null)) {
95 configChanged()?.catch((err) => { 100 configChanged()?.catch((err) => {
96 console.log('Unhandled error while listening for config changes', err); 101 console.log('Unhandled error while listening for config changes', err);
97 }); 102 });
98 } 103 }
99 }); 104 });
105 }
100 106
101 return () => watcher.close(); 107 dispose(): void {
108 this.watcher?.close();
102 } 109 }
103} 110}
diff --git a/packages/main/src/services/NativeThemeService.ts b/packages/main/src/services/NativeThemeService.ts
deleted file mode 100644
index 7a26c3c..0000000
--- a/packages/main/src/services/NativeThemeService.ts
+++ /dev/null
@@ -1,38 +0,0 @@
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 { nativeTheme } from 'electron';
22import type { IDisposer } from 'mobx-state-tree';
23import type { ThemeSource } from '@sophie/shared';
24
25export class NativeThemeService {
26 setThemeSource(themeSource: ThemeSource): void {
27 nativeTheme.themeSource = themeSource;
28 }
29
30 onShouldUseDarkColorsUpdated(callback: (shouldUseDarkColors: boolean) => void): IDisposer {
31 const wrappedCallback = () => {
32 callback(nativeTheme.shouldUseDarkColors);
33 };
34 wrappedCallback();
35 nativeTheme.on('updated', wrappedCallback);
36 return () => nativeTheme.off('updated', wrappedCallback);
37 }
38}