cleanup & sync summary
This commit is contained in:
parent
cd0007fc7d
commit
be4f67fb60
17 changed files with 637 additions and 207 deletions
|
|
@ -56,6 +56,10 @@ function progressGate(emit) {
|
|||
return push
|
||||
}
|
||||
|
||||
// Tree walks are dominated by per-file syscalls; a handful in flight keeps a
|
||||
// slow disk busy without flooding the event loop with thousands of them.
|
||||
const TREE_CONCURRENCY = 16
|
||||
|
||||
// Run `fn` over `items` with at most `limit` in flight, results in order.
|
||||
async function mapLimit(items, limit, fn) {
|
||||
const out = new Array(items.length)
|
||||
|
|
@ -90,7 +94,7 @@ async function treeSize(root) {
|
|||
} catch {
|
||||
return
|
||||
}
|
||||
for (const entry of entries) {
|
||||
await mapLimit(entries, TREE_CONCURRENCY, async entry => {
|
||||
const full = path.join(dir, entry.name)
|
||||
if (entry.isSymbolicLink()) {
|
||||
files++
|
||||
|
|
@ -102,7 +106,7 @@ async function treeSize(root) {
|
|||
bytes += (await fsp.stat(full)).size
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
await walk(root)
|
||||
return { files, bytes }
|
||||
|
|
@ -110,9 +114,12 @@ async function treeSize(root) {
|
|||
|
||||
// Copy a tree with symlinks recreated and permissions carried (copyFile
|
||||
// drops both); reports cumulative { done, bytes } through onProgress.
|
||||
// `failed` counts entries that could not be recreated: a caller that needs a
|
||||
// complete copy (an import) must not treat a partial tree as success.
|
||||
async function copyTree(src, dst, onProgress) {
|
||||
let done = 0
|
||||
let bytes = 0
|
||||
let failed = 0
|
||||
const gate = onProgress ? progressGate(onProgress) : null
|
||||
const tick = () => {
|
||||
if (gate) gate({ done, bytes }, false)
|
||||
|
|
@ -122,10 +129,16 @@ async function copyTree(src, dst, onProgress) {
|
|||
try {
|
||||
entries = await fsp.readdir(from, { withFileTypes: true })
|
||||
} catch {
|
||||
failed++
|
||||
return
|
||||
}
|
||||
await fsp.mkdir(to, { recursive: true })
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
await fsp.mkdir(to, { recursive: true })
|
||||
} catch {
|
||||
failed++
|
||||
return
|
||||
}
|
||||
await mapLimit(entries, TREE_CONCURRENCY, async entry => {
|
||||
const s = path.join(from, entry.name)
|
||||
const d = path.join(to, entry.name)
|
||||
if (entry.isSymbolicLink()) {
|
||||
|
|
@ -133,32 +146,46 @@ async function copyTree(src, dst, onProgress) {
|
|||
try {
|
||||
target = await fsp.readlink(s)
|
||||
} catch {}
|
||||
if (!target) continue
|
||||
if (!target) {
|
||||
failed++
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fsp.symlink(target, d)
|
||||
} catch {}
|
||||
} catch {
|
||||
failed++
|
||||
}
|
||||
done++
|
||||
tick()
|
||||
} else if (entry.isDirectory()) {
|
||||
await walk(s, d)
|
||||
} else if (entry.isFile()) {
|
||||
let size = 0
|
||||
let st = null
|
||||
try {
|
||||
size = (await fsp.stat(s)).size
|
||||
st = await fsp.stat(s)
|
||||
} catch {}
|
||||
if (!st) {
|
||||
failed++
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fsp.copyFile(s, d)
|
||||
await fsp.chmod(d, (await fsp.stat(s)).mode & 0o777)
|
||||
} catch {
|
||||
failed++
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fsp.chmod(d, st.mode & 0o777)
|
||||
} catch {}
|
||||
done++
|
||||
bytes += size
|
||||
bytes += st.size
|
||||
tick()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
await walk(src, dst)
|
||||
if (gate) gate({ done, bytes }, true)
|
||||
return { done, bytes }
|
||||
return { done, bytes, failed }
|
||||
}
|
||||
|
||||
// Imported trees come from media that lose permissions (archives, FAT, a
|
||||
|
|
@ -176,13 +203,13 @@ async function chmodTree(root) {
|
|||
} catch {
|
||||
return
|
||||
}
|
||||
for (const entry of entries) {
|
||||
await mapLimit(entries, TREE_CONCURRENCY, async entry => {
|
||||
const full = path.join(dir, entry.name)
|
||||
if (entry.isSymbolicLink()) continue
|
||||
if (entry.isSymbolicLink()) return
|
||||
if (entry.isDirectory()) {
|
||||
await walk(full)
|
||||
} else if (!entry.isFile()) {
|
||||
continue
|
||||
return
|
||||
}
|
||||
try {
|
||||
const before = (await fsp.stat(full)).mode & 0o7777
|
||||
|
|
@ -193,7 +220,7 @@ async function chmodTree(root) {
|
|||
else files++
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
})
|
||||
}
|
||||
await walk(root)
|
||||
return { files, dirs }
|
||||
|
|
@ -275,7 +302,7 @@ async function removeTree(root, onProgress) {
|
|||
} catch {
|
||||
return false
|
||||
}
|
||||
for (const entry of entries) {
|
||||
await mapLimit(entries, TREE_CONCURRENCY, async entry => {
|
||||
const full = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await walk(full)
|
||||
|
|
@ -284,7 +311,7 @@ async function removeTree(root, onProgress) {
|
|||
} catch {}
|
||||
dirs++
|
||||
tick()
|
||||
continue
|
||||
return
|
||||
}
|
||||
let size = 0
|
||||
try {
|
||||
|
|
@ -297,7 +324,7 @@ async function removeTree(root, onProgress) {
|
|||
files++
|
||||
bytes += size
|
||||
tick()
|
||||
}
|
||||
})
|
||||
try {
|
||||
await fsp.rmdir(dir)
|
||||
dirs++
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue