optimization: a long list of folders can be 2x faster, especially on network volumes (operations on a single entries are parallelized)
Massimo Melina committed
May 9, 2026 at 15:33 UTC
d97cb8f1d134ab2366897bedadad1056cc1aef3a
3 files changed
+21
-12
src/api.get_file_list.ts
+10
-6
@@ -117,10 +117,14 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, c, onl
117
const name = getNodeName(node)
118
const isFolder = nodeIsFolder(node)
119
try {
120
- const st = source ? await (node.stats || statWithTimeout(source).catch(e => {
121
- if (!isFolder || !node.children?.length) // folders with virtual children, keep them
122
- throw e
123
- })) : undefined
120
+ const [web, comment, st] = await Promise.all([
121
+ hasDefaultFile(node, ctx).then(x => x ? true : undefined),
122
+ node.comment ?? getCommentFor(source),
123
+ source ? (node.stats || statWithTimeout(source).catch(e => {
124
+ if (!isFolder || !node.children?.length) // folders with virtual children, keep them
125
+ throw e
126
+ })) : undefined
127
+ ])
128
// permissions of entries are sent as a difference with permissions of parent
129
const pl = node.can_list === WHO_NO_ONE ? 'l'
130
: !hasPermission(node, 'can_list', ctx) ? 'L'
@@ -142,9 +146,9 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, c, onl
146
url,
147
target: node.target,
148
order: node.order,
145
- comment: node.comment ?? await getCommentFor(source),
149
+ comment,
150
icon: getNodeIcon(node),
147
- web: await hasDefaultFile(node, ctx) ? true : undefined,
151
+ web,
152
}
153
}
154
catch {
src/vfs.ts
+3
-4
@@ -4,7 +4,7 @@ import fs from 'fs/promises'
4
import { basename, dirname, join, resolve } from 'path'
5
import {
6
makeMatcher, setHidden, onlyTruthy, isValidFileName, throw_, VfsPerms, Who, debounceAsync,
7
- isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS, removeStarting, HTTP_SERVER_ERROR, try_, matches,
7
+ isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS, HTTP_SERVER_ERROR, try_, matches, Promisable,
8
statWithTimeout, safeDecodeURIComponent, getUncHost,
9
} from './misc'
10
import Koa from 'koa'
@@ -44,7 +44,7 @@ export interface VfsNode extends VfsNodeStored { // include fields that are only
44
original?: VfsNode // if this is a temp node but reflecting an existing node
45
parent?: VfsNode // available when original is available (therefore, only for isTemp)
46
isFolder?: boolean // use nodeIsFolder() instead of relying on this field
47
- stats?: Promise<Stats>
47
+ stats?: Promisable<Stats>
48
}
49
50
export function permsFromParent(parent: VfsNode, child: VfsNode) {
@@ -381,8 +381,7 @@ export async function* walkNode(parent: VfsNode, {
381
const name = prefixPath + (renamed || path)
382
if (taken?.has(normalizeFilename(name))) // taken by vfs node above
383
return false // false just in case it's a folder
384
-
385
- const item: VfsNode = { name, isFolder, source: join(source, path), parent }
384
+ const item: VfsNode = { name, isFolder, source: join(source, path), parent, stats: entry.stats }
385
// masks containing '/' must be matched against the relative path while keeping walkDir recursion enabled
386
await pathMaskApplier(item, renamed || path)
387
if (await cantSee(item)) // can't see: don't produce and don't recur
src/walkDir.ts
+8
-2
@@ -62,7 +62,13 @@ export function walkDir(path: string, { depth = 0, hidden = true, parallelizeRec
62
work(Object.assign(Object.create(direntMethods), {
63
isDir: f.IS_DIRECTORY,
64
name: f.LONG_NAME,
65
- stats: { size: f.SIZE, birthtime: f.CREATION_TIME, mtime: f.LAST_WRITE_TIME } as Stats
65
+ stats: {
66
+ size: f.SIZE,
67
+ birthtime: f.CREATION_TIME, birthtimeMs: f.CREATION_TIME.getTime(),
68
+ mtime: f.LAST_WRITE_TIME, mtimeMs: f.LAST_WRITE_TIME.getTime(),
69
+ isFile: () => !f.IS_DIRECTORY,
70
+ isDirectory: () => f.IS_DIRECTORY,
71
+ } as Stats
72
}))
73
}, true))
74
}
@@ -139,4 +145,4 @@ for (const key of Reflect.ownKeys(Dirent.prototype)) {
145
DirentFromStats.prototype[name] = function () {
146
return this[kStats][name]();
147
};
142
-}
\ No newline at end of file
148
+}