fix: 双击文件夹条目读取失败清空该侧面板修复名不符实对比残留、枚举改手写BFS凑满即停修复超大目录枚举耗时(0.6.2)
This commit is contained in:
+26
-12
@@ -1,5 +1,5 @@
|
||||
import { readdir, stat, readFile, open } from 'fs/promises'
|
||||
import { join, relative, sep } from 'path'
|
||||
import { join } from 'path'
|
||||
|
||||
/**
|
||||
* 文件夹对比的纯逻辑模块(仅依赖 node:fs,vitest 以真实临时目录直接测试)。
|
||||
@@ -27,7 +27,7 @@ export interface ScanResult {
|
||||
entries: FolderEntry[]
|
||||
/** 因超出文件数量上限被截断(条目不完整,界面提示人工确认) */
|
||||
truncated: boolean
|
||||
/** 枚举发现的文件总数(含被截断未比对的文件,始终为真实总数) */
|
||||
/** 枚举发现的文件总数(截断时为已遍历下限:凑满上限即停止深入,不再统计剩余子树) */
|
||||
total: number
|
||||
}
|
||||
|
||||
@@ -50,24 +50,38 @@ const SAMPLE_BYTES = 8 * 1024
|
||||
*/
|
||||
export const SCAN_CONCURRENCY = 16
|
||||
|
||||
/** 递归枚举目录下全部普通文件(跳过子目录与 symlink),返回相对路径 → 大小 */
|
||||
/**
|
||||
* 手写 BFS 遍历目录树收集普通文件(相对路径统一 / 分隔,跳过 symlink 防环),
|
||||
* 凑满 maxFiles 即停止深入:readdir({recursive:true}) 只能枚举完整棵树、无法提前终止,
|
||||
* 误选超大目录(如含 node_modules)时枚举本身耗时数秒。
|
||||
* 截断后 total 为已遍历部分的文件数下限(剩余子树不再统计)。
|
||||
*/
|
||||
async function listFiles(
|
||||
root: string,
|
||||
maxFiles: number
|
||||
): Promise<{ files: Map<string, number>; truncated: boolean; total: number }> {
|
||||
const dirents = await readdir(root, { recursive: true, withFileTypes: true })
|
||||
const rels: string[] = []
|
||||
let total = 0
|
||||
let truncated = false
|
||||
for (const d of dirents) {
|
||||
if (!d.isFile()) continue
|
||||
total++
|
||||
if (rels.length >= maxFiles) {
|
||||
truncated = true
|
||||
continue
|
||||
const queue: Array<{ dir: string; prefix: string }> = [{ dir: root, prefix: '' }]
|
||||
while (queue.length > 0) {
|
||||
const { dir, prefix } = queue.shift()!
|
||||
const dirents = await readdir(dir, { withFileTypes: true })
|
||||
for (const d of dirents) {
|
||||
// Dirent 为 lstat 语义:symlink 既非 file 也非 directory,天然跳过(防环)
|
||||
if (d.isDirectory()) {
|
||||
queue.push({ dir: join(dir, d.name), prefix: prefix === '' ? d.name : `${prefix}/${d.name}` })
|
||||
} else if (d.isFile()) {
|
||||
total++
|
||||
if (rels.length >= maxFiles) {
|
||||
truncated = true
|
||||
} else {
|
||||
rels.push(prefix === '' ? d.name : `${prefix}/${d.name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
// parentPath 为 Node 20.12+ 的 Dirent 属性(旧名 path)
|
||||
rels.push(relative(root, join(d.parentPath, d.name)).split(sep).join('/'))
|
||||
// 凑满即停:不再处理队列中剩余的子目录(当前目录统计完整,保证确定性)
|
||||
if (truncated) break
|
||||
}
|
||||
// 并行 stat(libuv 线程池排队,文件数量受 maxFiles 约束)
|
||||
const stats = await Promise.all(rels.map((rel) => stat(join(root, rel))))
|
||||
|
||||
Reference in New Issue
Block a user