Skip to main content
← dvs documentation Rust API reference

dvs/
lib.rs

1pub mod audit;
2pub mod auth;
3pub mod backends;
4mod cache;
5pub mod config;
6pub mod error;
7mod files;
8pub mod git;
9mod gitignore;
10pub mod globbing;
11mod hashes;
12pub mod init;
13pub mod operation;
14pub mod paths;
15pub mod progress;
16pub(crate) mod utils;
17
18pub use backends::{Backend, RetrieveRequest, StoreRequest, StoreResult};
19pub use config::Compression;
20pub use error::{Error, ErrorCode, backend_error_code};
21pub use files::add::{AddDetail, AddReport, AddResult, add_files};
22pub use files::get::{
23    Action, Before, GetCode, GetCounts, GetOptions, GetOutcome, GetReport, GetRow, GetVerdict,
24    StoredCopy, get_files, shell_path,
25};
26pub use files::metadata::FileMetadata;
27pub use files::status::{Baseline, StatusFacts, StatusReport, WorkingCopy, inspect_status};
28pub use files::types::{Outcome, Status};
29pub use hashes::{HashAlg, Hashes};
30pub use operation::{
31    ByteTotals, CancelToken, Counts, FileRow, OperationContext, OperationObserver, OperationPlan,
32    OperationRow, RowState, RunOutcome, TransferObserver,
33};
34pub use paths::{AddPathStatus, DvsPaths, PathFilter, ProjectPath, find_repo_root};
35pub use progress::FileProgress;
36pub use utils::{format_size, set_num_threads};
37
38pub const VERSION: &str = env!("CARGO_PKG_VERSION");
39
40#[cfg(test)]
41pub mod testutil {
42    use fs_err as fs;
43    use std::path::{Path, PathBuf};
44    use tempfile::TempDir;
45
46    use crate::config::Config;
47    use crate::init::init;
48
49    /// Creates a temporary directory with a .git folder (simulating a git repo).
50    /// Returns the TempDir (owns the directory) and the path to the repo root.
51    ///
52    /// IMPORTANT: Keep the TempDir alive for the duration of the test,
53    /// otherwise the directory gets deleted.
54    pub fn create_temp_git_repo() -> (TempDir, PathBuf) {
55        let tmp = tempfile::tempdir().unwrap();
56        // The repo lives in a subdirectory of the tempdir so that storage can
57        // be placed as a sibling (outside the repo), matching real usage and
58        // the storage-inside-repo guard in `init`.
59        let repo_root = fs::canonicalize(tmp.path()).unwrap().join("repo");
60        fs::create_dir(&repo_root).unwrap();
61        fs::create_dir(repo_root.join(".git")).unwrap();
62        (tmp, repo_root)
63    }
64
65    /// Creates a file with the given content at the specified path.
66    /// Creates parent directories if needed.
67    /// Returns the full path to the created file.
68    pub fn create_file(dir: &Path, relative_path: &str, content: &[u8]) -> PathBuf {
69        let path = dir.join(relative_path);
70        if let Some(parent) = path.parent() {
71            fs::create_dir_all(parent).ok();
72        }
73        fs::write(&path, content).unwrap();
74        path
75    }
76
77    /// Initializes a DVS repository in the given directory.
78    /// Creates storage at `{repo_root}/../.storage` (a sibling, outside the
79    /// repo) and metadata at `{repo_root}/.dvs`.
80    /// Returns (config, dvs_metadata_dir).
81    pub fn init_dvs_repo(repo_root: &Path) -> (Config, PathBuf) {
82        let storage_dir = repo_root.parent().unwrap().join(".storage");
83        let config = Config::new_local(&storage_dir, None).unwrap();
84        init(repo_root, config.clone()).unwrap();
85        let dvs_dir = repo_root.join(".dvs");
86        (config, dvs_dir)
87    }
88
89    /// An observer that records each call as a short line, in order, plus
90    /// every finished row as JSON, and can cancel the operation once a given
91    /// number of transfers have started.
92    #[derive(Default)]
93    pub struct Recorder {
94        pub seen: std::sync::Mutex<Vec<String>>,
95        pub rows: std::sync::Mutex<Vec<serde_json::Value>>,
96        cancel: Option<(crate::CancelToken, usize)>,
97        count: std::sync::atomic::AtomicUsize,
98    }
99
100    impl Recorder {
101        /// Cancels `token` when the `n`th transfer starts.
102        pub fn cancelling_on_transfer(token: crate::CancelToken, n: usize) -> Self {
103            Self {
104                cancel: Some((token, n)),
105                ..Self::default()
106            }
107        }
108
109        pub fn seen(&self) -> Vec<String> {
110            self.seen.lock().unwrap().clone()
111        }
112
113        fn push(&self, line: String) {
114            self.seen.lock().unwrap().push(line);
115        }
116
117        fn tick(&self) {
118            if let Some((token, n)) = &self.cancel {
119                if self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1 == *n {
120                    token.cancel();
121                }
122            }
123        }
124    }
125
126    impl crate::OperationObserver for Recorder {
127        fn planned(&self, p: &crate::OperationPlan) {
128            let (files, bytes) = (p.files_total, p.bytes_total);
129            let (to_files, to_bytes) = (p.files_to_transfer, p.bytes_to_transfer);
130            self.push(format!("plan {files} {bytes} {to_files} {to_bytes}"));
131        }
132
133        fn hashing_started(&self) {
134            self.push("hash start".to_string());
135        }
136
137        fn hashing_finished(&self) {
138            self.push("hash end".to_string());
139        }
140
141        fn transfer_started(&self, path: &Path, _total_bytes: u64) -> Option<crate::FileProgress> {
142            self.push(format!("transfer {}", path.display()));
143            self.tick();
144            None
145        }
146
147        fn file_finished(&self, row: crate::FileRow<'_>) {
148            let (path, json) = match row {
149                crate::FileRow::Add(r) => (&r.path, serde_json::to_value(r)),
150                crate::FileRow::Get(r) => (&r.path, serde_json::to_value(r)),
151                crate::FileRow::Status(r) => (&r.path, serde_json::to_value(r)),
152            };
153            self.push(format!("row {}", path.display()));
154            self.rows.lock().unwrap().push(json.unwrap());
155        }
156    }
157}