aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/infrastructure
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-01-27 00:17:22 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-02-08 21:43:17 +0100
commit9546dc2aa39ab096ccc723786e718a739d0bdaf9 (patch)
tree9c3afc6155cc59f6dd1235397230aaa15a5f8cec /packages/main/src/infrastructure
parentrefactor: Apply shared store patches in batches (diff)
downloadsophie-9546dc2aa39ab096ccc723786e718a739d0bdaf9.tar.gz
sophie-9546dc2aa39ab096ccc723786e718a739d0bdaf9.tar.zst
sophie-9546dc2aa39ab096ccc723786e718a739d0bdaf9.zip
refactor: Coding conventions
Make sure that files have a default import with the same name as the file whenever possible to reduce surprise. Also shuffles around some file names for better legibility. Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages/main/src/infrastructure')
-rw-r--r--packages/main/src/infrastructure/config/ConfigFile.ts (renamed from packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts)70
-rw-r--r--packages/main/src/infrastructure/config/ConfigRepository.ts (renamed from packages/main/src/infrastructure/ConfigPersistence.ts)4
-rw-r--r--packages/main/src/infrastructure/config/ReadConfigResult.ts23
3 files changed, 62 insertions, 35 deletions
diff --git a/packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts b/packages/main/src/infrastructure/config/ConfigFile.ts
index 88d8bf8..193a20d 100644
--- a/packages/main/src/infrastructure/impl/FileBasedConfigPersistence.ts
+++ b/packages/main/src/infrastructure/config/ConfigFile.ts
@@ -17,48 +17,52 @@
17 * 17 *
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20
20import { watch } from 'node:fs'; 21import { watch } from 'node:fs';
21import { readFile, stat, writeFile } from 'node:fs/promises'; 22import { readFile, stat, writeFile } from 'node:fs/promises';
22import path from 'node:path'; 23import path from 'node:path';
23 24
24import JSON5 from 'json5'; 25import JSON5 from 'json5';
25import throttle from 'lodash-es/throttle'; 26import { throttle } from 'lodash-es';
26 27
27import type { Config } from '../../stores/SharedStore'; 28import type { Config } from '../../stores/SharedStore';
28import type Disposer from '../../utils/Disposer'; 29import type Disposer from '../../utils/Disposer';
29import { getLogger } from '../../utils/log'; 30import { getLogger } from '../../utils/log';
30import type ConfigPersistence from '../ConfigPersistence';
31import type { ReadConfigResult } from '../ConfigPersistence';
32 31
33const log = getLogger('fileBasedConfigPersistence'); 32import type ConfigRepository from './ConfigRepository';
33import type ReadConfigResult from './ReadConfigResult';
34
35const log = getLogger('ConfigFile');
36
37export default class ConfigFile implements ConfigRepository {
38 readonly #userDataDir: string;
39
40 readonly #configFileName: string;
34 41
35export default class FileBasedConfigPersistence implements ConfigPersistence { 42 readonly #configFilePath: string;
36 private readonly configFilePath: string;
37 43
38 private writingConfig = false; 44 #writingConfig = false;
39 45
40 private timeLastWritten: Date | undefined; 46 #timeLastWritten: Date | undefined;
41 47
42 constructor( 48 constructor(userDataDir: string, configFileName = 'config.json5') {
43 private readonly userDataDir: string, 49 this.#userDataDir = userDataDir;
44 private readonly configFileName: string = 'config.json5', 50 this.#configFileName = configFileName;
45 ) { 51 this.#configFilePath = path.join(userDataDir, configFileName);
46 this.configFileName = configFileName;
47 this.configFilePath = path.join(this.userDataDir, this.configFileName);
48 } 52 }
49 53
50 async readConfig(): Promise<ReadConfigResult> { 54 async readConfig(): Promise<ReadConfigResult> {
51 let configStr: string; 55 let configStr: string;
52 try { 56 try {
53 configStr = await readFile(this.configFilePath, 'utf8'); 57 configStr = await readFile(this.#configFilePath, 'utf8');
54 } catch (error) { 58 } catch (error) {
55 if ((error as NodeJS.ErrnoException).code === 'ENOENT') { 59 if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
56 log.debug('Config file', this.configFilePath, 'was not found'); 60 log.debug('Config file', this.#configFilePath, 'was not found');
57 return { found: false }; 61 return { found: false };
58 } 62 }
59 throw error; 63 throw error;
60 } 64 }
61 log.info('Read config file', this.configFilePath); 65 log.info('Read config file', this.#configFilePath);
62 return { 66 return {
63 found: true, 67 found: true,
64 data: JSON5.parse(configStr), 68 data: JSON5.parse(configStr),
@@ -69,32 +73,32 @@ export default class FileBasedConfigPersistence implements ConfigPersistence {
69 const configJson = JSON5.stringify(configSnapshot, { 73 const configJson = JSON5.stringify(configSnapshot, {
70 space: 2, 74 space: 2,
71 }); 75 });
72 this.writingConfig = true; 76 this.#writingConfig = true;
73 try { 77 try {
74 await writeFile(this.configFilePath, configJson, 'utf8'); 78 await writeFile(this.#configFilePath, configJson, 'utf8');
75 const { mtime } = await stat(this.configFilePath); 79 const { mtime } = await stat(this.#configFilePath);
76 log.trace('Config file', this.configFilePath, 'last written at', mtime); 80 log.trace('Config file', this.#configFilePath, 'last written at', mtime);
77 this.timeLastWritten = mtime; 81 this.#timeLastWritten = mtime;
78 } finally { 82 } finally {
79 this.writingConfig = false; 83 this.#writingConfig = false;
80 } 84 }
81 log.info('Wrote config file', this.configFilePath); 85 log.info('Wrote config file', this.#configFilePath);
82 } 86 }
83 87
84 watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer { 88 watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer {
85 log.debug('Installing watcher for', this.userDataDir); 89 log.debug('Installing watcher for', this.#userDataDir);
86 90
87 const configChanged = throttle(async () => { 91 const configChanged = throttle(async () => {
88 let mtime: Date; 92 let mtime: Date;
89 try { 93 try {
90 const stats = await stat(this.configFilePath); 94 const stats = await stat(this.#configFilePath);
91 mtime = stats.mtime; 95 mtime = stats.mtime;
92 log.trace('Config file last modified at', mtime); 96 log.trace('Config file last modified at', mtime);
93 } catch (error) { 97 } catch (error) {
94 if ((error as NodeJS.ErrnoException).code === 'ENOENT') { 98 if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
95 log.debug( 99 log.debug(
96 'Config file', 100 'Config file',
97 this.configFilePath, 101 this.#configFilePath,
98 'was deleted after being changed', 102 'was deleted after being changed',
99 ); 103 );
100 return; 104 return;
@@ -102,27 +106,27 @@ export default class FileBasedConfigPersistence implements ConfigPersistence {
102 throw error; 106 throw error;
103 } 107 }
104 if ( 108 if (
105 !this.writingConfig && 109 !this.#writingConfig &&
106 (this.timeLastWritten === undefined || mtime > this.timeLastWritten) 110 (this.#timeLastWritten === undefined || mtime > this.#timeLastWritten)
107 ) { 111 ) {
108 log.debug( 112 log.debug(
109 'Found a config file modified at', 113 'Found a config file modified at',
110 mtime, 114 mtime,
111 'whish is newer than last written', 115 'whish is newer than last written',
112 this.timeLastWritten, 116 this.#timeLastWritten,
113 ); 117 );
114 await callback(); 118 await callback();
115 } 119 }
116 }, throttleMs); 120 }, throttleMs);
117 121
118 const watcher = watch(this.userDataDir, { 122 const watcher = watch(this.#userDataDir, {
119 persistent: false, 123 persistent: false,
120 }); 124 });
121 125
122 watcher.on('change', (eventType, filename) => { 126 watcher.on('change', (eventType, filename) => {
123 if ( 127 if (
124 eventType === 'change' && 128 eventType === 'change' &&
125 (filename === this.configFileName || filename === null) 129 (filename === this.#configFileName || filename === null)
126 ) { 130 ) {
127 configChanged()?.catch((err) => { 131 configChanged()?.catch((err) => {
128 log.error('Unhandled error while listening for config changes', err); 132 log.error('Unhandled error while listening for config changes', err);
@@ -131,7 +135,7 @@ export default class FileBasedConfigPersistence implements ConfigPersistence {
131 }); 135 });
132 136
133 return () => { 137 return () => {
134 log.trace('Removing watcher for', this.configFilePath); 138 log.trace('Removing watcher for', this.#configFilePath);
135 watcher.close(); 139 watcher.close();
136 }; 140 };
137 } 141 }
diff --git a/packages/main/src/infrastructure/ConfigPersistence.ts b/packages/main/src/infrastructure/config/ConfigRepository.ts
index 184fa8d..0ce7fc1 100644
--- a/packages/main/src/infrastructure/ConfigPersistence.ts
+++ b/packages/main/src/infrastructure/config/ConfigRepository.ts
@@ -18,8 +18,8 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import type { Config } from '../stores/SharedStore'; 21import type { Config } from '../../stores/SharedStore';
22import type Disposer from '../utils/Disposer'; 22import type Disposer from '../../utils/Disposer';
23 23
24export type ReadConfigResult = 24export type ReadConfigResult =
25 | { found: true; data: unknown } 25 | { found: true; data: unknown }
diff --git a/packages/main/src/infrastructure/config/ReadConfigResult.ts b/packages/main/src/infrastructure/config/ReadConfigResult.ts
new file mode 100644
index 0000000..3b3ee55
--- /dev/null
+++ b/packages/main/src/infrastructure/config/ReadConfigResult.ts
@@ -0,0 +1,23 @@
1/*
2 * Copyright (C) 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
21type ReadConfigResult = { found: true; data: unknown } | { found: false };
22
23export default ReadConfigResult;