@samitouri / QOSami-HFS / commits / 2cd174eb

search: prioritize outer entries over deeper ones

Massimo Melina committed Jan 18, 2025 at 15:36 UTC 2cd174eb1ff2f3b29aed2e84f1bf94150a13f37f
2 files changed +46 -38
src/vfs.ts
+45 -37
@@ -275,15 +275,16 @@ export async function* walkNode(parent: VfsNode, {
275 onlyFiles = false,
276 }: { ctx?: Koa.Context,depth?: number, prefixPath?: string, requiredPerm?: undefined | keyof VfsPerms, onlyFolders?: boolean, onlyFiles?: boolean } = {}): AsyncIterableIterator<VfsNode> {
277 const { children, source } = parent
278 - const took = prefixPath ? undefined : new Set()
278 + const taken = prefixPath ? undefined : new Set()
279 const maskApplier = parentMaskApplier(parent)
280 const parentsCache = new Map() // we use this only if depth > 0
281 + const visitLater: any = []
282 if (children)
283 for (const child of children) {
284 if (await nodeIsDirectory(child) ? onlyFiles : onlyFolders) continue
285 const nodeName = getNodeName(child)
286 const name = prefixPath + nodeName
286 - took?.add(normalizeFilename(name))
287 + taken?.add(normalizeFilename(name))
288 const item = { ...child, name }
289 if (!await canSee(item)) continue
290 if (item.source) // real items must be accessible
@@ -294,48 +295,55 @@ export async function* walkNode(parent: VfsNode, {
295 parentsCache.set(name, item)
296 inheritMasks(item, parent, nodeName)
297 if (!ctx || hasPermission(item, 'can_list', ctx)) // check perm before recursion
297 - yield* walkNode(item, { ctx, depth: depth - 1, prefixPath: name + '/', requiredPerm, onlyFolders })
298 + visitLater.push([item, name]) // prioritize siblings
299 }
299 - if (!source)
300 - return
301 - if (requiredPerm && ctx // no permission, no reason to continue (at least for dynamic elements)
302 - && !hasPermission(parent, requiredPerm, ctx)
303 - && !masksCouldGivePermission(parent.masks, requiredPerm))
304 - return
305 -
300 try {
307 - let lastDir = prefixPath.slice(0, -1) || '.'
308 - parentsCache.set(lastDir, parent)
309 - for await (const entry of dirStream(source, { depth, onlyFolders, hidden: showHiddenFiles.get() })) {
310 - if (ctx?.isAborted()) break
311 - const {path} = entry
312 - const isFolder = entry.isDirectory()
313 - const name = prefixPath + (parent.rename?.[path] || path)
314 - if (took?.has(normalizeFilename(name))) continue
315 - if (depth) {
316 - const dir = dirname(name)
317 - if (dir !== lastDir)
318 - parent = parentsCache.get(lastDir = dir)
319 - }
301
321 - const item: VfsNode = {
322 - name,
323 - isFolder,
324 - source: join(source, path),
325 - rename: renameUnderPath(parent.rename, path),
302 + if (!source)
303 + return
304 + if (requiredPerm && ctx // no permission, no reason to continue (at least for dynamic elements)
305 + && !hasPermission(parent, requiredPerm, ctx)
306 + && !masksCouldGivePermission(parent.masks, requiredPerm))
307 + return
308 +
309 + try {
310 + let lastDir = prefixPath.slice(0, -1) || '.'
311 + parentsCache.set(lastDir, parent)
312 + for await (const entry of dirStream(source, { depth, onlyFolders, hidden: showHiddenFiles.get() })) {
313 + if (ctx?.isAborted()) break
314 + const {path} = entry
315 + const isFolder = entry.isDirectory()
316 + const name = prefixPath + (parent.rename?.[path] || path)
317 + if (taken?.has(normalizeFilename(name))) continue
318 + if (depth) {
319 + const dir = dirname(name)
320 + if (dir !== lastDir)
321 + parent = parentsCache.get(lastDir = dir)
322 + }
323 +
324 + const item: VfsNode = {
325 + name,
326 + isFolder,
327 + source: join(source, path),
328 + rename: renameUnderPath(parent.rename, path),
329 + }
330 + if (isFolder) // store it even if we can't see it (masks), as its children can be produced by dirStream
331 + parentsCache.set(name, item)
332 + if (!(onlyFiles && isFolder) && await canSee(item))
333 + yield item
334 + entry.closingBranch?.then(p =>
335 + parentsCache.delete(p || '.'))
336 }
327 - if (isFolder) // store it even if we can't see it (masks), as its children can be produced by dirStream
328 - parentsCache.set(name, item)
329 - if (!(onlyFiles && isFolder) && await canSee(item))
330 - yield item
331 - entry.closingBranch?.then(p =>
332 - parentsCache.delete(p || '.'))
337 }
338 + catch(e) {
339 + console.debug('walkNode', source, e) // ENOTDIR, or lacking permissions
340 + }
341 + parentsCache.clear() // hoping for faster GC
342 }
335 - catch(e) {
336 - console.debug('walkNode', source, e) // ENOTDIR, or lacking permissions
343 + finally {
344 + for (const [item, name] of visitLater)
345 + yield* walkNode(item, { ctx, depth: depth - 1, prefixPath: name + '/', requiredPerm, onlyFolders })
346 }
338 - parentsCache.clear() // hoping for faster GC
347
348 // item will be changed, so be sure to pass a temp node
349 async function canSee(item: VfsNode) {
tests/test.ts
+1 -1
@@ -97,7 +97,7 @@ describe('basics', () => {
97 it('protectFromAbove.list', reqList('/protectFromAbove/child/', { inList:['alfa.txt'] }))
98
99 const zipSize = 13010
100 - const zipOfs = 5000
100 + const zipOfs = 0x1359
101 it('zip.head', req('/f1/?get=zip', { empty:true, length:zipSize }, { method:'HEAD' }) )
102 it('zip.partial', req('/f1/?get=zip', { re:/^C3$/, length: 2 }, { headers: { Range: `bytes=${zipOfs}-${zipOfs+1}` } }) )
103 it('zip.partial.resume', req('/f1/?get=zip', { re:/^C3/, length:zipSize-zipOfs }, { headers: { Range: `bytes=${zipOfs}-` } }) )