fix: (regression 0.57.16) admin/shared: adding a folder from disk didn't work correctly until restart #1082
Massimo Melina committed
Sep 15, 2025 at 19:42 UTC
e638266c905c14823ec2a0a107a980f8f5619a04
5 files changed
+30
-13
admin/src/AccountForm.ts
+1
-1
@@ -6,7 +6,7 @@ import { Alert, Box } from '@mui/material'
6
import { apiCall } from './api'
7
import { alertDialog, useDialogBarColors } from './dialog'
8
import { formatTimestamp, isEqualLax, prefix, reactJoin, useIsMobile, wantArray } from './misc'
9
-import { Btn, Flex, IconBtn, NetmaskField, propsForModifiedValues, useLogBreakpoint } from './mui'
9
+import { Btn, Flex, IconBtn, NetmaskField, propsForModifiedValues } from './mui'
10
import { Account } from './AccountsPage'
11
import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
12
import { AutoDelete, Delete } from '@mui/icons-material'
src/api.vfs.ts
+1
-2
@@ -165,9 +165,8 @@ const apis: ApiHandlers = {
165
return HTTP_SERVER_ERROR
166
const idx = children.indexOf(node)
167
children.splice(idx, 1)
168
- await saveVfs()
168
return 0 // error code 0 is OK
170
- }))
169
+ })).finally(saveVfs)
170
}
171
},
172
src/config.ts
+2
-2
@@ -128,7 +128,7 @@ export async function setConfig(newCfg: Record<string,unknown>, save?: boolean)
128
const argCfg = !started && _.pickBy(newObj(configProps,
129
(x, k) => argv[k] ?? tryJson(considerEnvs ? process.env['HFS_' + k.toUpperCase().replaceAll('-','_')] : '', _.identity)),
130
x => x !== undefined)
131
- if (! _.isEmpty(argCfg)) {
131
+ if (!_.isEmpty(argCfg)) {
132
saveConfigAsap() // don't set `save` argument, as it would interfere below at check `save===false`
133
Object.assign(newCfg, argCfg)
134
}
@@ -149,7 +149,7 @@ export async function setConfig(newCfg: Record<string,unknown>, save?: boolean)
149
newCfg.hasOwnProperty(k) || apply(k, undefined, true)))
150
started = true
151
events.emit('configReady', startedWithoutConfig)
152
- if (version?.valueOf() !== VERSION) // be sure to save version
152
+ if (version?.valueOf() !== VERSION) // be sure to save the new version in the file
153
saveConfigAsap()
154
155
function apply(k: string, newV: any, isDefault=false) {
src/vfs.ts
+9
-6
@@ -3,7 +3,7 @@
3
import fs from 'fs/promises'
4
import { basename, dirname, join, resolve } from 'path'
5
import {
6
- makeMatcher, setHidden, onlyTruthy, isValidFileName, throw_, VfsPerms, Who,
6
+ makeMatcher, setHidden, onlyTruthy, isValidFileName, throw_, VfsPerms, Who, debounceAsync,
7
isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS, removeStarting, HTTP_SERVER_ERROR, try_, matches,
8
} from './misc'
9
import Koa from 'koa'
@@ -171,7 +171,9 @@ export async function getNodeByName(name: string, parent: VfsNode) {
171
}
172
173
export let vfs: VfsNode = {}
174
-defineConfig('vfs', vfs).sub(async data => {
174
+defineConfig('vfs', vfs).sub(reviewVfs)
175
+
176
+async function reviewVfs(data=vfs) {
177
await (async function recur(node) {
178
if (node.source && !node.children?.length && node.isFolder === undefined) {
179
const isFolder = /[\\/]$/.test(node.source) || (await nodeStats(node))?.isDirectory()
@@ -181,12 +183,13 @@ defineConfig('vfs', vfs).sub(async data => {
183
await Promise.allSettled(node.children.map(recur))
184
})(data)
185
vfs = data
184
-})
185
-
186
-export function saveVfs() {
187
- return setConfig({ vfs: _.cloneDeep(vfs) }, true)
186
}
187
188
+export const saveVfs = debounceAsync(async () => {
189
+ await reviewVfs()
190
+ await setConfig({ vfs }, true)
191
+})
192
+
193
export function isRoot(node: VfsNode) {
194
return node === vfs
195
}
tests/test.ts
+17
-2
@@ -269,6 +269,20 @@ describe('after-login', () => {
269
after(() => rm(resolve(__dirname, 'temp'), { recursive: true }).catch(() => 0))
270
})
271
272
+describe('admin', () => {
273
+ const auth = `${username}:${password}`
274
+ test('add folder', async () => {
275
+ const name = 'added'
276
+ try {
277
+ await reqApi('add_vfs', { source: '.', name, can_see: { this: false, children: true } }, 200, { auth })() // add an invisible folder
278
+ await reqList(name, { inList: ['plugins/'] })()
279
+ }
280
+ finally {
281
+ await reqApi('del_vfs', { uris: ['/'+name] }, 200, { auth })() // remove
282
+ }
283
+ })
284
+})
285
+
286
function login(usr: string, pwd=password) {
287
return srpClientSequence(usr, pwd, (cmd: string, params: any) =>
288
reqApi(cmd, params, (x,res)=> res.statusCode < 400)())
@@ -390,11 +404,12 @@ function req(url: string, test:Tester, { baseUrl, throttle, ...requestOptions }:
404
}
405
}
406
393
-function reqApi(api: string, params: object, test:Tester) {
407
+function reqApi(api: string, params: object, test:Tester, options:any={}) {
408
const isGet = api.startsWith('/')
409
return req(API+api, test, {
410
body: JSON.stringify(params),
397
- headers: isGet ? undefined : { 'x-hfs-anti-csrf': '1'}
411
+ headers: isGet ? undefined : { 'x-hfs-anti-csrf': '1'},
412
+ ...options,
413
})
414
}
415