dioxus_swdir_tree_core/scan.rs
1//! The async-boundary types: side effects **as data**, and the one
2//! blocking function that executes them.
3//!
4//! Transitions on [`crate::DirectoryTree`] never spawn tasks. When disk
5//! access is needed they return a [`ScanRequest`]; the embedding layer
6//! (a Dioxus coroutine, a thread pool, or a test) runs [`run`] on a
7//! worker and feeds the produced [`LoadPayload`] back through
8//! [`crate::DirectoryTree::on_loaded`].
9
10use std::path::PathBuf;
11
12use swdir::{ScanOptions, scan_dir_with_options};
13
14use crate::entry::LoadedEntry;
15use crate::error::ScanIssue;
16
17/// A scan the embedding layer must execute off the UI thread.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ScanRequest {
20 /// Directory to list (one level, non-recursive).
21 pub path: PathBuf,
22 /// Generation the result must carry to be accepted.
23 pub generation: u32,
24 /// Depth of `path` below the root, in components.
25 pub depth: u32,
26}
27
28/// A completed scan, ready to merge.
29#[derive(Debug, Clone, PartialEq)]
30pub struct LoadPayload {
31 /// Directory that was listed.
32 pub path: PathBuf,
33 /// Generation copied from the originating [`ScanRequest`].
34 pub generation: u32,
35 /// Depth copied from the originating [`ScanRequest`].
36 pub depth: u32,
37 /// The entries, or the failure.
38 pub result: Result<Vec<LoadedEntry>, ScanIssue>,
39}
40
41/// What [`crate::DirectoryTree::on_loaded`] did with a payload.
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
43pub struct LoadedOutcome {
44 /// `false` means the payload was stale (or its node vanished) and
45 /// the tree state is bit-identical to before the call.
46 pub accepted: bool,
47 /// Follow-up scans to execute. Always empty until prefetch
48 /// (RFC 009) lands; the field exists now so the signature never
49 /// breaks.
50 pub prefetch_requests: Vec<ScanRequest>,
51}
52
53impl LoadedOutcome {
54 /// Outcome for a silently discarded payload.
55 pub(crate) fn discarded() -> Self {
56 Self::default()
57 }
58
59 /// Outcome for an accepted merge with no follow-up work.
60 pub(crate) fn accepted() -> Self {
61 Self {
62 accepted: true,
63 prefetch_requests: Vec::new(),
64 }
65 }
66}
67
68/// Execute a [`ScanRequest`]: list the directory (sorted
69/// directories-first, name-ascending — the layout tree widgets expect)
70/// and package the result.
71///
72/// **Blocking.** Call this on a worker thread; never on the UI thread.
73pub fn run(request: &ScanRequest) -> LoadPayload {
74 let result = scan_dir_with_options(&request.path, &ScanOptions::default())
75 .map(|entries| entries.iter().map(LoadedEntry::from).collect())
76 .map_err(ScanIssue::from);
77 LoadPayload {
78 path: request.path.clone(),
79 generation: request.generation,
80 depth: request.depth,
81 result,
82 }
83}