file_engine/planner/outcome.rs
1use std::path::PathBuf;
2use std::time::Duration;
3
4use crate::error::Error;
5use crate::profiler::Entry;
6
7/// `#[non_exhaustive]`: a downstream `match` needs a `_` arm, so a new
8/// way for an operation to stop early isn't a breaking change. Same
9/// reasoning as `Progress`.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum StopReason {
13 Fatal,
14 AbortOnError,
15 Cancelled,
16 Undo,
17}
18
19/// Aggregate result of running an `ExecutionPlan`. Replaces a bare
20/// `Result<(), Error>` because `ErrorStrategy::ContinueAndCollect` can
21/// finish with a mix of successes and failures that a single `Result`
22/// can't represent.
23/// `#[non_exhaustive]`: an output type, built by this crate and read by
24/// the caller, so blocking downstream construction costs nothing —
25/// `Default` and `..`-destructuring both still work. Adding `duration`
26/// was a breaking change for exhaustive struct literals; marked now so
27/// the next field isn't, same reasoning as `Progress`.
28#[derive(Debug, Default)]
29#[non_exhaustive]
30pub struct OperationOutcome {
31 pub succeeded: Vec<Entry>,
32 /// Entries left untouched because the destination already held
33 /// byte-identical content — populated only when `.overwrite(false)`
34 /// (the default) is paired with `.skip_if_identical(true)` (the
35 /// `checksum` feature; see `CopyBuilder`/`MoveBuilder`). Disjoint
36 /// from `succeeded`: nothing was written, so counting it as a normal
37 /// success would overstate the bytes this run actually transferred.
38 /// Always empty otherwise.
39 pub skipped: Vec<Entry>,
40 pub failed: Vec<(Entry, Error)>,
41 /// Populated only by move's deferred deletion sweep: entries that
42 /// copied successfully but whose original source could not be
43 /// removed afterward (data duplicated, not lost). Copy never
44 /// populates this field.
45 pub cleanup_failed: Vec<(Entry, Error)>,
46 pub stopped_early: Option<StopReason>,
47 /// Populated only by the directory-permissions pass
48 /// (`.preserve_permissions()`), which always runs to completion
49 /// regardless of individual failures and never affects
50 /// `stopped_early` — a directory `chmod` failure is a best-effort
51 /// finishing touch, not an interruption of the actual data transfer.
52 /// Unconditional (not `#[cfg(unix)]`-gated), same reasoning as
53 /// `Entry.mode`: avoids a platform-conditional shape for a type with
54 /// many existing construction sites. Always empty on non-Unix or
55 /// when permission preservation wasn't requested.
56 pub directories_failed: Vec<(PathBuf, Error)>,
57 /// Populated only by `MoveManyBuilder`: whole-source failures that
58 /// happen before any per-file `Entry` exists for that source (the
59 /// atomic-rename fast path failed for a reason other than
60 /// cross-device, or the source vanished before it could be
61 /// scanned) — one entry per failed source, keyed by that source's
62 /// original path rather than an `Entry`, since none was ever built.
63 /// Always empty for `copy`/`move`.
64 pub sources_failed: Vec<(PathBuf, Error)>,
65 /// Wall time the operation took, stamped where the outcome is
66 /// produced for the caller. The counterpart to `Handle::elapsed()`
67 /// for after the handle has been consumed by `.await`.
68 ///
69 /// `SyncOutcome`'s two outcomes are timed per phase, so they don't
70 /// sum to the whole run — the diff that precedes them belongs to
71 /// neither. `Handle::elapsed()` remains the figure for the run as a
72 /// whole.
73 ///
74 /// `Duration::ZERO` on a phase that never ran (sync's delete sweep
75 /// when the copy phase stopped early) and on outcomes built by hand
76 /// in tests, which take the `Default`.
77 pub duration: Duration,
78}