better sync progress reporting
This commit is contained in:
parent
0f3fbaa418
commit
d4996988d9
3 changed files with 34 additions and 16 deletions
|
|
@ -915,14 +915,15 @@ async function handlePlan(state, session, url, req, res, hooks) {
|
|||
let inFlight = 0
|
||||
const settleOldest = () => Promise.race(jobs.splice(0, 1))
|
||||
await readNdjson(req, async entry => {
|
||||
if (!inlineSame(entry)) return
|
||||
// the sender announces its unit total before hashing starts (a control
|
||||
// record, no path): it carries the compare bar's width, so it must be
|
||||
// read before the entry filter drops it
|
||||
if (entry.unitBytes != null) {
|
||||
// the sender announces its unit total before hashing starts; it can
|
||||
// arrive after early compares (dirs/links), so it just overwrites
|
||||
unitTotalBytes = Number(entry.unitBytes) || 0
|
||||
session.barTotal = unitTotalBytes || 1
|
||||
return
|
||||
}
|
||||
if (!inlineSame(entry)) return
|
||||
entries.push(entry)
|
||||
if (!withinRoots(entry.path, owned)) return
|
||||
if (forced) {
|
||||
|
|
@ -968,14 +969,16 @@ async function handlePlan(state, session, url, req, res, hooks) {
|
|||
// this unit's files/bytes, reported as they arrive
|
||||
session.planned += upload.length
|
||||
session.plannedBytes += uploadBytes
|
||||
// the bar restarts for the copy phase
|
||||
session.unitWritten = 0
|
||||
session.barTotal = uploadBytes
|
||||
session.barBytes = 0
|
||||
// the unit's label is announced up front, so file events can name it
|
||||
session.unitLabel = label
|
||||
sendJson(res, 200, { ok: true, upload, same, entries: paths.size })
|
||||
// the compare is finished: report it full, then reset for the copy phase
|
||||
// (the first byte that arrives emits the new bar)
|
||||
session.barTotal = unitTotalBytes || session.barTotal || 1
|
||||
session.barBytes = session.barTotal
|
||||
announce({ unit: label }, true)
|
||||
session.unitWritten = 0
|
||||
session.unitLabel = label // named up front, so file events can label it
|
||||
session.barBytes = 0
|
||||
session.barTotal = uploadBytes
|
||||
sendJson(res, 200, { ok: true, upload, same, entries: paths.size })
|
||||
}
|
||||
|
||||
// One PUT, one file. The sender declares a content-length up front; if the
|
||||
|
|
@ -1095,6 +1098,9 @@ async function handleApply(session, req, res, hooks) {
|
|||
function handleEnd(state, session, res, hooks) {
|
||||
state.tokens.delete(session.token)
|
||||
if (state.session && state.session.token === session.token) state.session = null
|
||||
// a finished sync leaves a finished bar, even one that moved no files
|
||||
session.barTotal = session.barTotal || 1
|
||||
session.barBytes = session.barTotal
|
||||
sendJson(res, 200, { ok: true })
|
||||
emitReceive(hooks, session, 'done', { deleted: session.deleted || 0 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ function createPusher(api) {
|
|||
})
|
||||
}
|
||||
|
||||
function putFile(device, token, unitKey, rel, abs) {
|
||||
function putFile(device, token, unitKey, rel, abs, onBytes) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const settle = (fn, value) => {
|
||||
clearTimeout(timer)
|
||||
|
|
@ -151,6 +151,9 @@ function createPusher(api) {
|
|||
req.on('error', err => settle(reject, err))
|
||||
const stream = fs.createReadStream(abs)
|
||||
stream.on('error', err => settle(reject, err))
|
||||
// live bytes: a multi-GB file has to move the counter while it flies,
|
||||
// not only when its response lands (read = what this side hands over)
|
||||
if (onBytes) stream.on('data', chunk => onBytes(chunk.length))
|
||||
// pipe ends the request when the file ends; a body shorter than the
|
||||
// declared content-length (file changed mid-sync) wedges the receiver
|
||||
// - its own idle watchdog breaks that, this side's timer breaks every
|
||||
|
|
@ -366,9 +369,12 @@ function createPusher(api) {
|
|||
}
|
||||
return data
|
||||
}
|
||||
// snapshot per file, forward a few times per second (phase changes always)
|
||||
const gate = typeof api.progressGate === 'function' ? api.progressGate(progress) : data => progress(data)
|
||||
const emit = (phase, unit, done, total, force) => gate(snapshot(phase, unit, done, total), !!force)
|
||||
// Snapshot per file, forward a few times per second (phase changes
|
||||
// always). The snapshot is built when the gate flushes, not when it is
|
||||
// pushed: a throttled byte count must not be one gate window old.
|
||||
const emitNow = data => progress(typeof data === 'function' ? data() : data)
|
||||
const gate = typeof api.progressGate === 'function' ? api.progressGate(emitNow) : emitNow
|
||||
const emit = (phase, unit, done, total, force) => gate(() => snapshot(phase, unit, done, total), !!force)
|
||||
// consent may wait on a human: say so before anything else happens
|
||||
emit('wait', null, null, null, true)
|
||||
|
||||
|
|
@ -488,15 +494,19 @@ function createPusher(api) {
|
|||
let done = 0
|
||||
const send = async entry => {
|
||||
if (failed || abort.dead) return
|
||||
const res = await putFile(device, token, unit.key, entry.path, filesPath(entry.path))
|
||||
const res = await putFile(device, token, unit.key, entry.path, filesPath(entry.path), delta => {
|
||||
summary.bytes += delta
|
||||
barBytes += delta
|
||||
emit('copy', unit, allDone + done, allTotal + pending.length)
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
failed = new Error(`upload-failed-${res.status}`)
|
||||
return
|
||||
}
|
||||
done++
|
||||
summary.uploaded++
|
||||
summary.bytes += entry.size || 0
|
||||
barBytes += entry.size || 0
|
||||
// the byte counters already moved while the file was read; the
|
||||
// completion still has to push the file count next
|
||||
emit('copy', unit, allDone + done, allTotal + pending.length)
|
||||
}
|
||||
if (typeof api.mapLimit === 'function') await api.mapLimit(pending, UPLOAD_CONCURRENCY, send)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue