better code (watchLoad)
Massimo Melina committed
Feb 11, 2022 at 11:31 UTC
37fb5132ddc4099aa2deb61b5d37622cf68eda2c
4 files changed
+28
-21
src/config.ts
+2
-3
@@ -1,7 +1,6 @@
1
import EventEmitter from 'events'
2
import { argv } from './const'
3
import { watchLoad } from './watchLoad'
4
-import fs from 'fs/promises'
4
import yaml from 'yaml'
5
import _ from 'lodash'
6
import { debounceAsync, objSameKeys, onOffMap } from './misc'
@@ -19,7 +18,7 @@ let state: Record<string, any> = {}
18
const emitter = new EventEmitter()
19
emitter.setMaxListeners(10_000)
20
const path = argv.config || process.env.HFS_CONFIG || PATH
22
-watchLoad(path, values => setConfig(values, false), {
21
+const { save } = watchLoad(path, values => setConfig(values, false), {
22
failedOnFirstAttempt(){
23
console.log("No config file, using defaults")
24
setConfig({}, false)
@@ -114,7 +113,7 @@ export const saveConfigAsap = debounceAsync(async () => {
113
txt = ''
114
else
115
return
117
- fs.writeFile(path, txt)
116
+ save(path, txt)
117
.catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)))
118
})
119
src/perm.ts
+5
-12
@@ -1,4 +1,3 @@
1
-import fs from 'fs/promises'
1
import _ from 'lodash'
2
import yaml from 'yaml'
3
import { hashPassword } from './crypt'
@@ -81,25 +80,19 @@ export async function updateAccount(account: Account, changer?:Changer) {
80
}
81
82
let saving = false
84
-let justSaved = false
83
+let watchResult: ReturnType<typeof watchLoad>
84
const saveAccountsAsap = _.debounce(() => {
85
saving = true
87
- fs.writeFile(path, yaml.stringify({ accounts }, { lineWidth:1000 })) // we don't want big numbers to be folded
88
- .then(() => justSaved = true,
89
- err => console.error('Failed at saving accounts file, please ensure it is writable.', String(err)))
86
+ watchResult.save(path, yaml.stringify({ accounts }, { lineWidth:1000 })) // we don't want big numbers to be folded
87
+ .catch(err => console.error('Failed at saving accounts file, please ensure it is writable.', String(err)))
88
.finally(()=> saving = false)
89
}, 200) // group burst of requests
90
93
-let watcher: undefined | (()=>void)
91
subscribeConfig({ k:'accounts', defaultValue:'accounts.yaml' }, v => {
95
- watcher?.()
92
+ watchResult?.unwatch()
93
if (!v)
94
return applyAccounts({})
98
- watcher = watchLoad(path = v, async data => {
99
- if (justSaved) {
100
- justSaved = false
101
- return
102
- }
95
+ watchResult = watchLoad(path = v, async data => {
96
if (saving) return
97
const a = data?.accounts
98
if (!a)
src/plugins.ts
+1
-1
@@ -129,7 +129,7 @@ async function rescan() {
129
if (plugins[k]) // already loaded
130
continue
131
f = resolve(f) // without this, import won't work
132
- const unwatch = watchLoad(f, async () => {
132
+ const { unwatch } = watchLoad(f, async () => {
133
try {
134
console.log(plugins[k] ? 'reloading plugin' : 'loading plugin', k)
135
const data = await import(f)
src/watchLoad.ts
+20
-5
@@ -1,4 +1,5 @@
1
import { FSWatcher, watch } from 'fs'
2
+import fs from 'fs/promises'
3
import yaml from 'yaml'
4
import { debounceAsync, readFileBusy } from './misc'
5
@@ -6,22 +7,36 @@ export type WatchLoadCanceller = () => void
7
8
interface Options { failedOnFirstAttempt?: ()=>void }
9
9
-export function watchLoad(path:string, parser:(data:any)=>void|Promise<void>, { failedOnFirstAttempt }:Options={}): WatchLoadCanceller {
10
+type WriteFile = typeof fs.writeFile
11
+interface WatchLoadReturn { unwatch:WatchLoadCanceller, save:WriteFile }
12
+export function watchLoad(path:string, parser:(data:any)=>void|Promise<void>, { failedOnFirstAttempt }:Options={}): WatchLoadReturn {
13
let doing = false
14
let watcher: FSWatcher | undefined
15
const debounced = debounceAsync(load, 500)
16
let retry: NodeJS.Timeout
17
+ let saving: Promise<unknown> | undefined
18
init()
19
if (!watcher)
20
failedOnFirstAttempt?.()
17
- return () => {
18
- watcher?.close()
19
- clearTimeout(retry)
21
+ return {
22
+ unwatch(){
23
+ watcher?.close()
24
+ clearTimeout(retry)
25
+ watcher = undefined
26
+ },
27
+ save(...args:Parameters<WriteFile>) {
28
+ return Promise.resolve(saving).then(() => // wait in case another is ongoing
29
+ saving = fs.writeFile(...args).finally(() => // save but also keep track of the current operation
30
+ saving = undefined)) // clear
31
+ }
32
}
33
34
function init() {
35
try {
24
- watcher = watch(path, debounced)
36
+ watcher = watch(path, ()=> {
37
+ if (!saving)
38
+ debounced().then()
39
+ })
40
debounced().then() // if file is not accessible watch will throw and we won't get here
41
}
42
catch {