aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/infrastructure/config/impl/ConfigFile.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/infrastructure/config/impl/ConfigFile.ts')
-rw-r--r--packages/main/src/infrastructure/config/impl/ConfigFile.ts23
1 files changed, 17 insertions, 6 deletions
diff --git a/packages/main/src/infrastructure/config/impl/ConfigFile.ts b/packages/main/src/infrastructure/config/impl/ConfigFile.ts
index 4f0d4f0..8f0cc3f 100644
--- a/packages/main/src/infrastructure/config/impl/ConfigFile.ts
+++ b/packages/main/src/infrastructure/config/impl/ConfigFile.ts
@@ -22,7 +22,7 @@ import { watch } from 'node:fs';
22import { readFile, stat, writeFile } from 'node:fs/promises'; 22import { readFile, stat, writeFile } from 'node:fs/promises';
23import path from 'node:path'; 23import path from 'node:path';
24 24
25import { throttle } from 'lodash-es'; 25import { debounce } from 'lodash-es';
26 26
27import type Disposer from '../../../utils/Disposer.js'; 27import type Disposer from '../../../utils/Disposer.js';
28import getLogger from '../../../utils/getLogger.js'; 28import getLogger from '../../../utils/getLogger.js';
@@ -33,6 +33,7 @@ import type { ReadConfigResult } from '../ConfigRepository.js';
33const log = getLogger('ConfigFile'); 33const log = getLogger('ConfigFile');
34 34
35export const CONFIG_FILE_NAME = 'settings.json'; 35export const CONFIG_FILE_NAME = 'settings.json';
36export const DEFAULT_CONFIG_CHANGE_DEBOUNCE_MS = 10;
36 37
37export default class ConfigFile implements ConfigRepository { 38export default class ConfigFile implements ConfigRepository {
38 private readonly configFilePath: string; 39 private readonly configFilePath: string;
@@ -44,6 +45,7 @@ export default class ConfigFile implements ConfigRepository {
44 constructor( 45 constructor(
45 private readonly userDataDir: string, 46 private readonly userDataDir: string,
46 private readonly configFileName = CONFIG_FILE_NAME, 47 private readonly configFileName = CONFIG_FILE_NAME,
48 private readonly debounceTime = DEFAULT_CONFIG_CHANGE_DEBOUNCE_MS,
47 ) { 49 ) {
48 this.configFilePath = path.join(userDataDir, configFileName); 50 this.configFilePath = path.join(userDataDir, configFileName);
49 } 51 }
@@ -59,7 +61,7 @@ export default class ConfigFile implements ConfigRepository {
59 } 61 }
60 throw error; 62 throw error;
61 } 63 }
62 log.info('Read config file', this.configFilePath); 64 log.debug('Read config file', this.configFilePath);
63 return { 65 return {
64 found: true, 66 found: true,
65 contents, 67 contents,
@@ -82,10 +84,10 @@ export default class ConfigFile implements ConfigRepository {
82 log.debug('Wrote config file', this.configFilePath); 84 log.debug('Wrote config file', this.configFilePath);
83 } 85 }
84 86
85 watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer { 87 watchConfig(callback: () => Promise<void>): Disposer {
86 log.debug('Installing watcher for', this.userDataDir); 88 log.debug('Installing watcher for', this.userDataDir);
87 89
88 const configChanged = throttle(async () => { 90 const configChanged = debounce(async () => {
89 let mtime: Date; 91 let mtime: Date;
90 try { 92 try {
91 const stats = await stat(this.configFilePath); 93 const stats = await stat(this.configFilePath);
@@ -116,9 +118,13 @@ export default class ConfigFile implements ConfigRepository {
116 'which is newer than last written', 118 'which is newer than last written',
117 this.timeLastWritten, 119 this.timeLastWritten,
118 ); 120 );
119 await callback(); 121 try {
122 await callback();
123 } catch (error) {
124 log.error('Callback error while listening for config changes', error);
125 }
120 } 126 }
121 }, throttleMs); 127 }, this.debounceTime);
122 128
123 const watcher = watch( 129 const watcher = watch(
124 this.userDataDir, 130 this.userDataDir,
@@ -127,8 +133,12 @@ export default class ConfigFile implements ConfigRepository {
127 recursive: false, 133 recursive: false,
128 }, 134 },
129 (_eventType, filename) => { 135 (_eventType, filename) => {
136 // We handle both `rename` and `change` events for maximum portability.
137 // This may result in multiple calls to `configChanged` for a single config change,
138 // so we debounce it with a short (imperceptible) delay.
130 if (filename === this.configFileName || filename === null) { 139 if (filename === this.configFileName || filename === null) {
131 configChanged()?.catch((err) => { 140 configChanged()?.catch((err) => {
141 // This should never happen, because `configChanged` handles all exceptions.
132 log.error( 142 log.error(
133 'Unhandled error while listening for config changes', 143 'Unhandled error while listening for config changes',
134 err, 144 err,
@@ -141,6 +151,7 @@ export default class ConfigFile implements ConfigRepository {
141 return () => { 151 return () => {
142 log.trace('Removing watcher for', this.configFilePath); 152 log.trace('Removing watcher for', this.configFilePath);
143 watcher.close(); 153 watcher.close();
154 configChanged.cancel();
144 }; 155 };
145 } 156 }
146} 157}