Skip to main content

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}