aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/reactions/synchronizeConfig.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/reactions/synchronizeConfig.ts')
-rw-r--r--packages/main/src/reactions/synchronizeConfig.ts73
1 files changed, 41 insertions, 32 deletions
diff --git a/packages/main/src/reactions/synchronizeConfig.ts b/packages/main/src/reactions/synchronizeConfig.ts
index 9436c16..6044ee7 100644
--- a/packages/main/src/reactions/synchronizeConfig.ts
+++ b/packages/main/src/reactions/synchronizeConfig.ts
@@ -18,51 +18,56 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import deepEqual from 'deep-equal';
22import { debounce } from 'lodash-es'; 21import { debounce } from 'lodash-es';
23import { reaction } from 'mobx'; 22import { reaction } from 'mobx';
24import ms from 'ms';
25 23
26import type ConfigRepository from '../infrastructure/config/ConfigRepository'; 24import type ConfigRepository from '../infrastructure/config/ConfigRepository.js';
27import type SharedStore from '../stores/SharedStore'; 25import type SharedStore from '../stores/SharedStore.js';
28import type Config from '../stores/config/Config'; 26import type Config from '../stores/config/Config.js';
29import type Disposer from '../utils/Disposer'; 27import type Disposer from '../utils/Disposer.js';
30import { getLogger } from '../utils/log'; 28import getLogger from '../utils/getLogger.js';
31 29
32const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); 30const DEFAULT_CONFIG_DEBOUNCE_TIME_MS = 1000;
33 31
34const log = getLogger('synchronizeConfig'); 32const log = getLogger('synchronizeConfig');
35 33
34export function serializeConfig(config: Config): string {
35 return `${JSON.stringify(config, undefined, 2)}\n`;
36}
37
36export default async function synchronizeConfig( 38export default async function synchronizeConfig(
37 sharedStore: SharedStore, 39 sharedStore: SharedStore,
38 repository: ConfigRepository, 40 repository: ConfigRepository,
39 debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, 41 debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME_MS,
40): Promise<Disposer> { 42): Promise<Disposer> {
41 let lastConfigOnDisk: Config | undefined; 43 let lastConfigOnDisk: string | undefined;
42 44
43 async function writeConfig(): Promise<void> { 45 async function writeConfig(serializedConfig: string): Promise<void> {
44 const { config } = sharedStore; 46 await repository.writeConfig(serializedConfig);
45 await repository.writeConfig(config); 47 lastConfigOnDisk = serializedConfig;
46 lastConfigOnDisk = config;
47 } 48 }
48 49
49 async function readConfig(): Promise<boolean> { 50 async function readConfig(): Promise<boolean> {
50 const result = await repository.readConfig(); 51 const result = await repository.readConfig();
51 if (result.found) { 52 if (result.found) {
53 const { contents } = result;
54 if (contents === lastConfigOnDisk) {
55 // No need to re-apply config if we have already applied it.
56 return true;
57 }
52 try { 58 try {
53 // This cast is unsound if the config file is invalid, 59 // This cast is unsound if the config file is invalid,
54 // but we'll throw an error in the end anyways. 60 // but we'll throw an error in the end anyways.
55 sharedStore.loadConfig(result.data as Config); 61 const data = JSON.parse(contents) as Config;
62 sharedStore.loadConfig(data);
63 log.info('Loaded config');
56 } catch (error) { 64 } catch (error) {
57 log.error('Failed to apply config snapshot', result.data, error); 65 log.error('Failed to apply config snapshot', contents, error);
58 return true; 66 return true;
59 } 67 }
60 lastConfigOnDisk = sharedStore.config; 68 lastConfigOnDisk = serializeConfig(sharedStore.config);
61 // We can't use `comparer.structural` from `mobx`, because 69 if (contents !== lastConfigOnDisk) {
62 // it handles missing values and `undefined` values differently, 70 await writeConfig(lastConfigOnDisk);
63 // but JSON5 is unable to distinguish them.
64 if (!deepEqual(result.data, lastConfigOnDisk, { strict: true })) {
65 await writeConfig();
66 } 71 }
67 } 72 }
68 return result.found; 73 return result.found;
@@ -70,20 +75,23 @@ export default async function synchronizeConfig(
70 75
71 if (!(await readConfig())) { 76 if (!(await readConfig())) {
72 log.info('Config file was not found'); 77 log.info('Config file was not found');
73 await writeConfig(); 78 const serializedConfig = serializeConfig(sharedStore.config);
79 await writeConfig(serializedConfig);
74 log.info('Created config file'); 80 log.info('Created config file');
75 } 81 }
76 82
83 const debouncedSerializeConfig = debounce(() => {
84 const serializedConfig = serializeConfig(sharedStore.config);
85 if (serializedConfig !== lastConfigOnDisk) {
86 writeConfig(serializedConfig).catch((error) => {
87 log.error('Failed to write config on config change', error);
88 });
89 }
90 }, debounceTime);
91
77 const disposeReaction = reaction( 92 const disposeReaction = reaction(
78 () => sharedStore.config, 93 () => sharedStore.config,
79 debounce((config) => { 94 debouncedSerializeConfig,
80 // We can compare snapshots by reference, since it is only recreated on store changes.
81 if (!deepEqual(config, lastConfigOnDisk, { strict: true })) {
82 writeConfig().catch((error) => {
83 log.error('Failed to write config on config change', error);
84 });
85 }
86 }, debounceTime),
87 ); 95 );
88 96
89 const disposeWatcher = repository.watchConfig(async () => { 97 const disposeWatcher = repository.watchConfig(async () => {
@@ -92,10 +100,11 @@ export default async function synchronizeConfig(
92 } catch (error) { 100 } catch (error) {
93 log.error('Failed to read config', error); 101 log.error('Failed to read config', error);
94 } 102 }
95 }, debounceTime); 103 });
96 104
97 return () => { 105 return () => {
98 disposeWatcher(); 106 disposeWatcher();
99 disposeReaction(); 107 disposeReaction();
108 debouncedSerializeConfig.flush();
100 }; 109 };
101} 110}