fix: vfs changes were not saved

Massimo Melina committed Feb 10, 2022 at 12:13 UTC 46ffe43a736373d7fdc4caba6c842743b7cb058b
4 files changed +26 -19
src/adminApis.ts
+1 -1
@@ -15,7 +15,7 @@ export const adminApis: ApiHandlers = {
15
16 async set_config({ values }) {
17 if (values)
18 - setConfig(values, true)
18 + await setConfig(values)
19 return {}
20 },
21
src/api.vfs.ts
+3 -8
@@ -1,9 +1,8 @@
1 -import { getNodeName, nodeIsDirectory, vfs, VfsNode } from './vfs'
1 +import { getNodeName, nodeIsDirectory, saveVfs, vfs, VfsNode } from './vfs'
2 import _ from 'lodash'
3 import { stat } from 'fs/promises'
4 import { ApiError, ApiHandlers } from './apis'
5 import { dirname } from 'path'
6 -import { saveConfigAsap } from './config'
6 import glob, { Entry } from 'fast-glob'
7 import { enforceFinal, isWindows, isWindowsDrive } from './misc'
8 import { exec } from 'child_process'
@@ -17,10 +16,6 @@ type VfsAdmin = {
16 children?: VfsAdmin[]
17 } & Omit<VfsNode, 'type' | 'children'>
18
20 -function saveVfs() {
21 - saveConfigAsap()
22 -}
23 -
19 const apis: ApiHandlers = {
20
21 async get_vfs() {
@@ -55,7 +50,7 @@ const apis: ApiHandlers = {
50 Object.assign(n, pickProps(props, ['name','source','hidden','forbid','perm','hide','remove']))
51 if (getNodeName(_.omit(n, ['name'])) === n.name) // name only if necessary
52 delete n.name
58 - saveVfs()
53 + await saveVfs()
54 return n
55 },
56
@@ -67,7 +62,7 @@ const apis: ApiHandlers = {
62 return new ApiError(403, 'invalid under')
63 const a = n.children || (n.children = [])
64 a.unshift({ source, name })
70 - saveVfs()
65 + await saveVfs()
66 return {}
67 },
68
src/config.ts
+16 -8
@@ -19,10 +19,10 @@ let state: Record<string, any> = {}
19 const emitter = new EventEmitter()
20 emitter.setMaxListeners(10_000)
21 const path = argv.config || process.env.HFS_CONFIG || PATH
22 -watchLoad(path, setConfig, {
22 +watchLoad(path, values => setConfig(values, false), {
23 failedOnFirstAttempt(){
24 console.log("No config file, using defaults")
25 - setConfig({})
25 + setConfig({}, false)
26 }
27 })
28
@@ -31,6 +31,8 @@ interface ConfigProps<T> {
31 caster?:(argV:string)=> T
32 }
33 export function defineConfig<T>(k: string, definition: ConfigProps<T>) {
34 + if (definition.defaultValue !== undefined)
35 + definition.defaultValue = _.cloneDeep(definition.defaultValue)
36 configProps[k] = definition
37 if (!definition.caster)
38 if (typeof definition.defaultValue === 'number')
@@ -49,7 +51,7 @@ export function subscribeConfig<T>({ k, ...definition }:{ k:string } & ConfigPro
51 if (started) {
52 let v = state[k]
53 if (v === undefined)
52 - v = defaultValue
54 + v = _.cloneDeep(defaultValue)
55 if (v !== undefined)
56 cb(v)
57 }
@@ -68,15 +70,18 @@ export function getWholeConfig({ omit=[], only=[] }: { omit:string[], only:strin
70 return _.cloneDeep(copy)
71 }
72
71 -export function setConfig(newCfg: Record<string,any>, partial=false) {
73 +// pass a value to `save` to force saving decision, or leave undefined for auto
74 +export function setConfig(newCfg: Record<string,any>, save?: boolean) {
75 if (!newCfg)
76 newCfg = {}
77 for (const k in newCfg)
78 check(k)
79 const oldKeys = Object.keys(state)
80 oldKeys.push(...Object.keys(configProps))
78 - if (partial)
79 - return saveConfigAsap()
81 + if (save) {
82 + saveConfigAsap().then()
83 + return
84 + }
85 for (const k of oldKeys)
86 if (!newCfg.hasOwnProperty(k))
87 check(k)
@@ -86,16 +91,19 @@ export function setConfig(newCfg: Record<string,any>, partial=false) {
91 const oldV = started ? getConfig(k) : state[k] // from second time consider also defaultValue
92 const newV = newCfg[k]
93 const { caster, defaultValue } = configProps[k] ?? {}
89 - let v = newV === undefined ? defaultValue : newV
94 + let v = newV ?? _.cloneDeep(defaultValue) // if we have an object we may get into troubles letting others change ours
95 if (caster)
96 v = caster(v)
97 const j = JSON.stringify(v)
98 if (j === JSON.stringify(oldV)) return // no change
94 - if (j === JSON.stringify(defaultValue)) // if we move away from the default value and then come back, we restore the initial state (undefined)
99 + if (newV === undefined // optimization: we know in this case it's equal to the default
100 + || j === JSON.stringify(defaultValue)) // if we move away from the default value and then come back, we restore the initial state (undefined)
101 delete state[k]
102 else
103 state[k] = v
104 emitter.emit('new.'+k, v, oldV)
105 + if (save === undefined)
106 + saveConfigAsap().then()
107 }
108 }
109
src/vfs.ts
+6 -2
@@ -5,7 +5,7 @@ import { dirTraversal, enforceFinal, isDirectory, isWindows, onlyTruthy } from '
5 import Koa from 'koa'
6 import glob from 'fast-glob'
7 import _ from 'lodash'
8 -import { subscribeConfig } from './config'
8 +import { setConfig, subscribeConfig } from './config'
9
10 export interface VfsNode {
11 isTemp?: true, // this node was spawned by a source-d node and is not part of the vfs tree
@@ -72,9 +72,13 @@ export class Vfs {
72 }
73
74 export const vfs = new Vfs()
75 -subscribeConfig<VfsNode>({ k: 'vfs', defaultValue: {} }, data =>
75 +subscribeConfig<VfsNode>({ k: 'vfs', defaultValue: vfs.root }, data =>
76 vfs.root = data)
77
78 +export function saveVfs() {
79 + return setConfig({ vfs: _.cloneDeep(vfs.root) }, true)
80 +}
81 +
82 function findChildByName(name:string, node:VfsNode) {
83 const { rename } = node
84 if (rename) // @ts-ignore