diff options
Diffstat (limited to 'packages/main/src/infrastructure/config/impl/ConfigFile.ts')
-rw-r--r-- | packages/main/src/infrastructure/config/impl/ConfigFile.ts | 23 |
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'; | |||
22 | import { readFile, stat, writeFile } from 'node:fs/promises'; | 22 | import { readFile, stat, writeFile } from 'node:fs/promises'; |
23 | import path from 'node:path'; | 23 | import path from 'node:path'; |
24 | 24 | ||
25 | import { throttle } from 'lodash-es'; | 25 | import { debounce } from 'lodash-es'; |
26 | 26 | ||
27 | import type Disposer from '../../../utils/Disposer.js'; | 27 | import type Disposer from '../../../utils/Disposer.js'; |
28 | import getLogger from '../../../utils/getLogger.js'; | 28 | import getLogger from '../../../utils/getLogger.js'; |
@@ -33,6 +33,7 @@ import type { ReadConfigResult } from '../ConfigRepository.js'; | |||
33 | const log = getLogger('ConfigFile'); | 33 | const log = getLogger('ConfigFile'); |
34 | 34 | ||
35 | export const CONFIG_FILE_NAME = 'settings.json'; | 35 | export const CONFIG_FILE_NAME = 'settings.json'; |
36 | export const DEFAULT_CONFIG_CHANGE_DEBOUNCE_MS = 10; | ||
36 | 37 | ||
37 | export default class ConfigFile implements ConfigRepository { | 38 | export 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 | } |