optimization: avoid some double computation

Massimo Melina committed May 7, 2023 at 20:43 UTC e6af026b4abbf1a455e3e7a8c90664254278f0d4
1 file changed +21 -8
src/vfs.ts
+21 -8
@@ -2,7 +2,7 @@
2
3 import fs from 'fs/promises'
4 import { basename, dirname, join, resolve } from 'path'
5 -import { dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, typedKeys, makeMatcher } from './misc'
5 +import { dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, typedKeys, makeMatcher, setHidden } from './misc'
6 import Koa from 'koa'
7 import _ from 'lodash'
8 import { defineConfig, setConfig } from './config'
@@ -44,6 +44,7 @@ export interface VfsNode extends Partial<VfsPerm> {
44 isTemp?: true // this node doesn't belong to the tree and was created by necessity
45 original?: VfsNode // if this is a temp node but reflecting an existing node
46 parent?: VfsNode // available when original is available
47 + isFolder?: boolean
48 }
49
50 export const defaultPerms: VfsPerm = {
@@ -83,6 +84,7 @@ export function isSameFilenameAs(name: string) {
84
85 export function applyParentToChild(child: VfsNode | undefined, parent: VfsNode, name?: string) {
86 const ret: VfsNode = {
87 + isFolder: child?.children?.length ? true : undefined, // allow child to overwrite this property
88 ...child,
89 original: child,
90 isTemp: true,
@@ -111,10 +113,10 @@ export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=
113 }
114 // does the tree node have a child that goes by this name?
115 const child = parent.children?.find(isSameFilenameAs(name))
114 - if (!child && !parent.source) return // on tree or on disk
116 + if (!child && !parent.source) return // on tree or on disk, or it doesn't exist
117
118 const ret = applyParentToChild(child, parent, name)
117 - if (child) // yes
119 + if (child)
120 return urlToNode(rest, ctx, ret, getRest)
121 let onDisk = name
122 if (parent.rename) { // reverse the mapping
@@ -131,7 +133,10 @@ export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=
133 if (rest)
134 return urlToNode(rest, ctx, ret, getRest)
135 if (ret.source)
134 - try { await fs.stat(ret.source) } // check existence
136 + try {
137 + const st = await fs.stat(ret.source) // check existence
138 + ret.isFolder = st.isDirectory()
139 + }
140 catch {
141 if (!getRest)
142 return
@@ -166,7 +171,14 @@ export function getNodeName(node: VfsNode) {
171 }
172
173 export async function nodeIsDirectory(node: VfsNode) {
169 - return Boolean(!node.source || await isDirectory(node.source))
174 + if (node.isFolder !== undefined)
175 + return node.isFolder
176 + const isFolder = Boolean(node.children?.length || !node.source || await isDirectory(node.source))
177 + if (node.isTemp)
178 + node.isFolder = isFolder
179 + else
180 + setHidden(node, { isFolder }) // don't make it to the storage
181 + return isFolder
182 }
183
184 export function hasPermission(node: VfsNode, perm: keyof VfsPerm, ctx: Koa.Context): boolean {
@@ -236,7 +248,7 @@ export async function* walkNode(parent:VfsNode, ctx?: Koa.Context, depth:number=
248 const map = new Map()
249 map.set(lastDir, parent)
250 // it's important to keep using dirStream in deep-mode, as it is manyfold faster (it parallelizes)
239 - for await (const [path, isDir] of dirStream(source, depth)) {
251 + for await (const [path, isFolder] of dirStream(source, depth)) {
252 if (ctx?.req.aborted)
253 return
254 const name = prefixPath + (parent.rename?.[path] || path)
@@ -247,13 +259,14 @@ export async function* walkNode(parent:VfsNode, ctx?: Koa.Context, depth:number=
259 parent = map.get(lastDir = dir)
260 }
261
250 - const item = {
262 + const item: VfsNode = {
263 name,
264 + isFolder,
265 source: join(source, path),
266 rename: renameUnderPath(parent.rename, path),
267 }
268 if (!canSee(item)) continue
256 - if (isDir)
269 + if (isFolder)
270 map.set(name, item)
271 yield item
272 }