Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,12 @@ These move: check each project's current docs before relying on a cell.
rules are later edited. Genuinely overlapping sources are still rejected.
- Concurrent, paced executor with retries and resumable uploads. An upload
interrupted by a quit or crash resumes byte-for-byte across restarts, with
the recovery shown live in the app ("Recovering interrupted upload - 8.2 GB
of 88.6 GB") instead of an unlabeled startup phase.
the recovery shown live in the app - both the per-file byte progress
("Recovering interrupted upload - 8.2 GB of 88.6 GB") and, while the recovery
works through the interrupted uploads themselves, the op counts
("Recovering - resuming 7 of 18 uploads") - instead of an unlabeled startup
phase. The startup recovery runs its remote lookups concurrently, so a large
backlog of interrupted uploads no longer delays the first scan by a minute.
- Configurable OS priority (`low` by default) for the scan, upload reads, and
bundle builds, so backups yield CPU and disk to whatever is in the foreground.
- Battery and network awareness: backups defer on battery and on metered or
Expand All @@ -143,6 +147,10 @@ These move: check each project's current docs before relying on a cell.
- macOS menu bar live status: configurable metrics next to the tray icon while
backing up (upload speed, percent, files, time remaining) and a configurable
idle readout (last backup age or today's uploaded total).
- Quit never freezes the app. Quitting mid-backup hides the window instantly and
lets the current cycle finish in the background; the tray shows a distinct
"quitting" icon, and its menu offers "Force quit now" if you would rather not
wait (in-flight uploads resume from where they stopped on the next launch).
- Settings organized as a sidebar of focused pages (General, Schedule & Power,
Performance, a macOS/Windows platform page, Network, Privacy & Data,
Advanced) with search; About is identity-only, reached from the sidebar
Expand Down
756 changes: 702 additions & 54 deletions crates/driven-core/src/executor.rs

Large diffs are not rendered by default.

25 changes: 19 additions & 6 deletions crates/driven-core/src/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1213,8 +1213,19 @@ impl SyncOrchestrator {
let now = self.clock.now_ms();
{
let mut guard = last_emit.lock().await;
// Issue #301: the pass now emits an OP tick per
// recovered pending op as well as the BYTE ticks of
// a streaming resume, so "is this the final tick"
// has to consider BOTH dimensions - a byte tick that
// finished its file is not final if ops remain, and
// an op tick (which carries 0/0 bytes) is final only
// once every op is done. Anything else throttles to
// ~1/s on the INJECTED clock (the module's
// determinism rule - never tokio/Instant time).
let is_final =
p.bytes_done >= p.bytes_total && p.ops_done >= p.ops_total;
let throttled = matches!(*guard, Some(last) if now - last < RECOVER_EMIT_MIN_INTERVAL_MS)
&& p.bytes_done < p.bytes_total;
&& !is_final;
if throttled {
return;
}
Expand All @@ -1225,6 +1236,8 @@ impl SyncOrchestrator {
path: p.path,
bytes_done: p.bytes_done,
bytes_total: p.bytes_total,
ops_done: p.ops_done,
ops_total: p.ops_total,
})
.await;
})
Expand Down Expand Up @@ -3465,11 +3478,11 @@ mod tests {
if ticks > 1 {
let total = 1_000u64;
for i in 0..ticks {
on_recover(crate::executor::RecoverProgress {
path: "big/file.bin".to_string(),
bytes_done: i * total / (ticks - 1),
bytes_total: total,
})
on_recover(crate::executor::RecoverProgress::bytes(
"big/file.bin".to_string(),
i * total / (ticks - 1),
total,
))
.await;
}
}
Expand Down
31 changes: 22 additions & 9 deletions crates/driven-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,22 +480,35 @@ pub enum OrchestratorState {
/// Checking the power / network gates (DESIGN s5.7) before starting a
/// batch. A failed gate transitions to [`OrchestratorState::Paused`].
PowerCheck,
/// The startup reconcile is recovering an interrupted upload by
/// byte-level resume (2026-08-14 follow-up): re-reading the local file
/// and pushing the unacked tail. Previously this ran invisibly inside
/// [`OrchestratorState::PowerCheck`] - for the incident's 88 GB disk
/// image that meant a multi-minute "Starting backup..." sweep while
/// 140 Mbps of upload showed nowhere in the UI.
/// The startup reconcile is recovering interrupted work (2026-08-14
/// follow-up; issue #301): adopting or re-queuing each pending op, and
/// byte-level resuming any op that carries a live upload session.
/// Previously this ran invisibly inside [`OrchestratorState::PowerCheck`] -
/// for the incident's 88 GB disk image that meant a multi-minute
/// "Starting backup..." sweep while 140 Mbps of upload showed nowhere in
/// the UI, and (issue #301) a 22-op reconcile meant a full minute of
/// generic "Starting backup..." with nothing moving at all.
Recovering {
/// Source whose pending op is being recovered.
source_id: SourceId,
/// Source-relative path of the file being resumed (display only).
/// Source-relative path of the op being recovered (display only).
/// Empty when the tick reports whole-pass op progress rather than one
/// file's byte progress.
path: String,
/// Bytes the destination has acked so far (the resume's progress
/// numerator; starts at the previously-acked offset, not 0).
/// numerator; starts at the previously-acked offset, not 0). Zero on
/// an op-progress tick, which carries no byte dimension.
bytes_done: u64,
/// The session's total byte count.
/// The session's total byte count. Zero on an op-progress tick.
bytes_total: u64,
/// Issue #301: pending ops of this source's pass recovered so far.
/// The reconcile spends most of its wall clock on one remote
/// round trip per op (a `metadata` read or a `find_by_op_uuid`
/// lookup), which carries no bytes at all - so without this counter
/// the UI has nothing to show for the majority of a recovery.
ops_done: u64,
/// Issue #301: total pending ops in this source's pass.
ops_total: u64,
},
/// Walking + diffing one source's local tree (SPEC s6). `scanned` is a
/// running count of files visited, for a live progress readout.
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ tray:
settings: "Settings"
restore: "Restore"
quit: "Quit Driven"
quitting_status: "Quitting - finishing current backup..."
force_quit: "Force quit now"
menu_status:
line1: "Backing up - %{percent}, %{eta} left"
line1_no_eta: "Backing up - %{percent}"
Expand Down Expand Up @@ -58,6 +60,7 @@ tray:
needs_reauth: "Driven needs to sign in again"
error: "Driven - error, attention needed"
suspending: "Suspending..."
quitting: "Driven - quitting (finishing backup, right-click to force quit)"

notifications:
first_sync_complete:
Expand Down
Loading