You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Replaced `cancelled: AtomicBool` + `skip_rollback: AtomicBool` with `OperationIntent` enum (`Running` → `RollingBack` → `Stopped`) backed by `AtomicU8`
- `cancel_write_operation` does validated state transitions; `cancel_all_write_operations` transitions to `Stopped` (no silent background rollback on teardown)
- Rollback runs synchronously on the same `spawn_blocking` thread with progress events — no more fire-and-forget `rollback_in_background`
- Backend emits decreasing `files_done`/`bytes_done` during rollback so the frontend's progress bars count backwards from the cancellation point
- Frontend reuses the same progress view for rollback — no separate UI, no flicker. Rollback button shows "Rolling back..." disabled, Cancel stays active to stop the rollback
- Removed `rollback_in_background()` from `CopyTransaction`
- Added `RollingBack` variant to `WriteOperationPhase` (backend + frontend)
- Updated ~50 cancellation check sites across 14 files to use `is_cancelled(&state.intent)`
or teardown), `RollingBack → Stopped` (user cancels the rollback). `Stopped` is terminal. The `is_cancelled()` helper
60
+
returns true for both `RollingBack` and `Stopped`, so the 40+ cancellation check sites just call `is_cancelled(&state.intent)`.
61
+
62
+
**Two-layer cancellation.**`AtomicU8` for fast in-loop checks. `run_cancellable` wraps blocking operations (e.g.,
57
63
network-mount copies that may block indefinitely) in a separate thread, polling the flag every 100ms via `mpsc::channel`.
58
64
59
-
**`CopyTransaction` rollback: sync vs async.** Two rollback paths: `rollback()` (synchronous, for error paths where
60
-
cleanup must complete before the error event) and `rollback_in_background()` (fire-and-forget on a detached thread, for
61
-
user-initiated cancel where the UI must respond instantly). `rollback_in_background` sets `committed = true` to prevent
62
-
`Drop` from triggering a synchronous double-rollback, then moves the file/dir lists into the detached thread. The
63
-
synchronous `rollback()` and auto-rollback-on-panic via `Drop` remain unchanged. Delete operations are not rollbackable.
65
+
**`CopyTransaction` rollback: sync with progress.**`rollback()` (synchronous, for error paths) and tracked
66
+
`rollback_with_progress()` in `copy.rs` (for user-initiated rollback — emits `write-progress` events with
67
+
`phase: RollingBack`, checks for `Stopped` between file deletions so the user can cancel the rollback). Auto-rollback
68
+
via `Drop` remains as a panic safety net. Delete operations are not rollbackable.
64
69
65
70
**Symlinks never dereferenced.** All stat calls use `symlink_metadata`. Symlink loop detection uses a `HashSet<PathBuf>`
66
71
of canonicalized paths.
@@ -72,7 +77,10 @@ rename temp → dest, delete backup. The original is intact until step 3 complet
72
77
Frontend calls `resolve_write_conflict(operation_id, resolution, apply_to_all)` which stores a `ConflictResolutionResponse`
73
78
and notifies the condvar. `cancel_write_operation` also notifies the condvar to unblock.
74
79
75
-
**`skip_rollback` is stored inverted.**`cancel_write_operation(rollback: bool)` stores `!rollback` in `skip_rollback`.
80
+
**`cancel_write_operation` does state transitions.**`rollback=true` → `Running → RollingBack`, `rollback=false` →
81
+
`Running → Stopped` or `RollingBack → Stopped`. First caller's decision wins — subsequent calls with different intent
82
+
are no-ops (unless transitioning from `RollingBack → Stopped`). `cancel_all_write_operations` always transitions to
83
+
`Stopped` (teardown should never silently roll back without visual feedback).
76
84
77
85
**Scan preview caching.**`start_scan_preview` runs a background scan, caches the result in `SCAN_PREVIEW_RESULTS`. The
78
86
actual `copy_files_start` can consume the cache via `preview_id` in `WriteOperationConfig`, skipping a redundant scan.
@@ -121,9 +129,9 @@ the progress dialog and offer cancel, even if a network mount is stalled.
121
129
emits `write-error` as a safety net, and `Err(join_error)` handles panics. Double-emit is harmless because the
122
130
frontend's `handleError` removes all listeners on first receipt.
123
131
124
-
**Background cleanup is best-effort.**`rollback_in_background`, `remove_file_in_background`, and
125
-
`remove_dir_all_in_background` run on detached threads. If the network mount disconnects or the app exits, partial
126
-
files or staging directories may remain on disk. These use the `.cmdr-` prefix, so they're recognizable.
132
+
**Background cleanup is best-effort.**`remove_file_in_background` and `remove_dir_all_in_background` run on detached
133
+
threads (used for temp/backup file cleanup, not for user-visible rollback). If the network mount disconnects or the app
134
+
exits, partial files or staging directories may remain on disk. These use the `.cmdr-` prefix, so they're recognizable.
127
135
128
136
**`volume_copy` path is incomplete.** The three `volume_*` files are Phase 5 work, but are publicly re-exported from `mod.rs` and at least partially wired up.
0 commit comments