alopex_chirps_file_transfer/
options.rs

1use serde::{Deserialize, Serialize};
2use std::time::Duration;
3
4/// Default chunk size in bytes (1 MiB).
5pub const DEFAULT_CHUNK_SIZE: usize = 1024 * 1024;
6/// Minimum supported chunk size in bytes.
7pub const MIN_CHUNK_SIZE: usize = 64 * 1024;
8/// Maximum supported chunk size in bytes.
9pub const MAX_CHUNK_SIZE: usize = 16 * 1024 * 1024;
10const DEFAULT_CONCURRENCY: usize = 4;
11const DEFAULT_CLOCK_SKEW_TOLERANCE_SECS: u64 = 2;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
14/// Compression options for transfer payloads.
15pub enum CompressionAlgorithm {
16    /// Do not compress payloads.
17    #[default]
18    None,
19    /// Compress payloads with Zstd at the default level.
20    Zstd,
21    /// Compress payloads with Zstd at the provided level.
22    ZstdLevel(i32),
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
26/// Hash algorithms supported for integrity verification.
27pub enum HashAlgorithm {
28    /// SHA-256 hash.
29    #[default]
30    Sha256,
31    /// Blake3 hash.
32    Blake3,
33    /// XXHash64 hash.
34    XxHash64,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
38/// Behavior for what happens to the source after a successful transfer.
39pub enum TransferMode {
40    /// Keep the source and copy the data.
41    #[default]
42    Copy,
43    /// Remove the source after a successful transfer.
44    Move,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48/// Direction to synchronize files between local and remote.
49pub enum SyncDirection {
50    /// Local to remote.
51    Push,
52    /// Remote to local.
53    Pull,
54    /// Synchronize in both directions.
55    Bidirectional,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
59/// Strategy for resolving conflicts when both sides differ.
60pub enum ConflictResolution {
61    /// Prefer the newer file based on modification time.
62    #[default]
63    NewerWins,
64    /// Prefer the local/source copy.
65    SourceWins,
66    /// Prefer the remote/target copy.
67    TargetWins,
68    /// Require manual resolution.
69    Manual,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
73/// Sort key for file listings.
74pub enum SortBy {
75    /// Sort by file name.
76    #[default]
77    Name,
78    /// Sort by file size.
79    Size,
80    /// Sort by last modification timestamp.
81    ModifiedTime,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85/// Retry policy for transient transfer failures.
86pub struct RetryPolicy {
87    pub max_retries: u8,
88    pub initial_delay: Duration,
89    pub max_delay: Duration,
90    pub backoff_multiplier: f64,
91    pub jitter: bool,
92}
93
94impl Default for RetryPolicy {
95    fn default() -> Self {
96        RetryPolicy {
97            max_retries: 3,
98            initial_delay: Duration::from_millis(100),
99            max_delay: Duration::from_secs(10),
100            backoff_multiplier: 2.0,
101            jitter: true,
102        }
103    }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107/// Options that control how a transfer is performed.
108pub struct TransferOptions {
109    pub chunk_size: usize,
110    pub concurrency: usize,
111    pub compression: CompressionAlgorithm,
112    pub bandwidth_limit: Option<u64>,
113    pub retry_policy: RetryPolicy,
114    pub verify_on_complete: bool,
115    pub hash_algorithm: HashAlgorithm,
116    pub resumable: bool,
117    pub overwrite: bool,
118    pub mode: TransferMode,
119    pub preserve_metadata: bool,
120}
121
122impl Default for TransferOptions {
123    fn default() -> Self {
124        TransferOptions {
125            chunk_size: DEFAULT_CHUNK_SIZE,
126            concurrency: DEFAULT_CONCURRENCY,
127            compression: CompressionAlgorithm::None,
128            bandwidth_limit: None,
129            retry_policy: RetryPolicy::default(),
130            verify_on_complete: true,
131            hash_algorithm: HashAlgorithm::Sha256,
132            resumable: true,
133            overwrite: false,
134            mode: TransferMode::Copy,
135            preserve_metadata: true,
136        }
137    }
138}
139
140impl TransferOptions {
141    /// Returns options with the chunk size updated.
142    ///
143    /// # Panics
144    /// This method does not panic.
145    pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
146        self.chunk_size = chunk_size;
147        self
148    }
149
150    /// Returns options with the concurrency updated.
151    ///
152    /// # Panics
153    /// This method does not panic.
154    pub fn with_concurrency(mut self, concurrency: usize) -> Self {
155        self.concurrency = concurrency;
156        self
157    }
158
159    /// Returns options with the compression algorithm updated.
160    ///
161    /// # Panics
162    /// This method does not panic.
163    pub fn with_compression(mut self, compression: CompressionAlgorithm) -> Self {
164        self.compression = compression;
165        self
166    }
167
168    /// Returns options with the bandwidth limit updated.
169    ///
170    /// # Panics
171    /// This method does not panic.
172    pub fn with_bandwidth_limit(mut self, bandwidth_limit: Option<u64>) -> Self {
173        self.bandwidth_limit = bandwidth_limit;
174        self
175    }
176
177    /// Returns options with the retry policy updated.
178    ///
179    /// # Panics
180    /// This method does not panic.
181    pub fn with_retry_policy(mut self, retry_policy: RetryPolicy) -> Self {
182        self.retry_policy = retry_policy;
183        self
184    }
185
186    /// Returns options with the verify-on-complete flag updated.
187    ///
188    /// # Panics
189    /// This method does not panic.
190    pub fn with_verify_on_complete(mut self, verify_on_complete: bool) -> Self {
191        self.verify_on_complete = verify_on_complete;
192        self
193    }
194
195    /// Returns options with the hash algorithm updated.
196    ///
197    /// # Panics
198    /// This method does not panic.
199    pub fn with_hash_algorithm(mut self, hash_algorithm: HashAlgorithm) -> Self {
200        self.hash_algorithm = hash_algorithm;
201        self
202    }
203
204    /// Returns options with the resumable flag updated.
205    ///
206    /// # Panics
207    /// This method does not panic.
208    pub fn with_resumable(mut self, resumable: bool) -> Self {
209        self.resumable = resumable;
210        self
211    }
212
213    /// Returns options with the overwrite flag updated.
214    ///
215    /// # Panics
216    /// This method does not panic.
217    pub fn with_overwrite(mut self, overwrite: bool) -> Self {
218        self.overwrite = overwrite;
219        self
220    }
221
222    /// Returns options with the transfer mode updated.
223    ///
224    /// # Panics
225    /// This method does not panic.
226    pub fn with_mode(mut self, mode: TransferMode) -> Self {
227        self.mode = mode;
228        self
229    }
230
231    /// Returns options with metadata preservation updated.
232    ///
233    /// # Panics
234    /// This method does not panic.
235    pub fn with_preserve_metadata(mut self, preserve_metadata: bool) -> Self {
236        self.preserve_metadata = preserve_metadata;
237        self
238    }
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
242/// Options that control a sync operation.
243pub struct SyncOptions {
244    pub transfer: TransferOptions,
245    pub direction: SyncDirection,
246    pub conflict_resolution: ConflictResolution,
247    pub follow_symlinks: bool,
248    pub clock_skew_tolerance: Duration,
249}
250
251impl Default for SyncOptions {
252    fn default() -> Self {
253        SyncOptions {
254            transfer: TransferOptions::default(),
255            direction: SyncDirection::Push,
256            conflict_resolution: ConflictResolution::NewerWins,
257            follow_symlinks: false,
258            clock_skew_tolerance: Duration::from_secs(DEFAULT_CLOCK_SKEW_TOLERANCE_SECS),
259        }
260    }
261}
262
263impl SyncOptions {
264    /// Returns sync options with transfer options updated.
265    ///
266    /// # Panics
267    /// This method does not panic.
268    pub fn with_transfer(mut self, transfer: TransferOptions) -> Self {
269        self.transfer = transfer;
270        self
271    }
272
273    /// Returns sync options with the sync direction updated.
274    ///
275    /// # Panics
276    /// This method does not panic.
277    pub fn with_direction(mut self, direction: SyncDirection) -> Self {
278        self.direction = direction;
279        self
280    }
281
282    /// Returns sync options with the conflict resolution updated.
283    ///
284    /// # Panics
285    /// This method does not panic.
286    pub fn with_conflict_resolution(mut self, conflict_resolution: ConflictResolution) -> Self {
287        self.conflict_resolution = conflict_resolution;
288        self
289    }
290
291    /// Returns sync options with follow-symlinks updated.
292    ///
293    /// # Panics
294    /// This method does not panic.
295    pub fn with_follow_symlinks(mut self, follow_symlinks: bool) -> Self {
296        self.follow_symlinks = follow_symlinks;
297        self
298    }
299
300    /// Returns sync options with the clock skew tolerance updated.
301    ///
302    /// # Panics
303    /// This method does not panic.
304    pub fn with_clock_skew_tolerance(mut self, clock_skew_tolerance: Duration) -> Self {
305        self.clock_skew_tolerance = clock_skew_tolerance;
306        self
307    }
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize, Default)]
311/// Options for file and directory removal.
312pub struct RemoveOptions {
313    pub recursive: bool,
314    pub ignore_not_found: bool,
315}
316
317impl RemoveOptions {
318    /// Returns options with recursive removal enabled or disabled.
319    ///
320    /// # Panics
321    /// This method does not panic.
322    pub fn with_recursive(mut self, recursive: bool) -> Self {
323        self.recursive = recursive;
324        self
325    }
326
327    /// Returns options with ignore-not-found enabled or disabled.
328    ///
329    /// # Panics
330    /// This method does not panic.
331    pub fn with_ignore_not_found(mut self, ignore_not_found: bool) -> Self {
332        self.ignore_not_found = ignore_not_found;
333        self
334    }
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
338/// Options for listing files and directories.
339pub struct ListOptions {
340    pub recursive: bool,
341    pub include_hidden: bool,
342    pub files_only: bool,
343    pub directories_only: bool,
344    pub pattern: Option<String>,
345    pub limit: usize,
346    pub sort_by: SortBy,
347}
348
349impl Default for ListOptions {
350    fn default() -> Self {
351        ListOptions {
352            recursive: false,
353            include_hidden: false,
354            files_only: false,
355            directories_only: false,
356            pattern: None,
357            limit: 0,
358            sort_by: SortBy::Name,
359        }
360    }
361}
362
363impl ListOptions {
364    /// Returns options with recursive traversal enabled or disabled.
365    ///
366    /// # Panics
367    /// This method does not panic.
368    pub fn with_recursive(mut self, recursive: bool) -> Self {
369        self.recursive = recursive;
370        self
371    }
372
373    /// Returns options with hidden files included or excluded.
374    ///
375    /// # Panics
376    /// This method does not panic.
377    pub fn with_include_hidden(mut self, include_hidden: bool) -> Self {
378        self.include_hidden = include_hidden;
379        self
380    }
381
382    /// Returns options with files-only filtering enabled or disabled.
383    ///
384    /// # Panics
385    /// This method does not panic.
386    pub fn with_files_only(mut self, files_only: bool) -> Self {
387        self.files_only = files_only;
388        self
389    }
390
391    /// Returns options with directories-only filtering enabled or disabled.
392    ///
393    /// # Panics
394    /// This method does not panic.
395    pub fn with_directories_only(mut self, directories_only: bool) -> Self {
396        self.directories_only = directories_only;
397        self
398    }
399
400    /// Returns options with an optional name pattern filter.
401    ///
402    /// # Panics
403    /// This method does not panic.
404    pub fn with_pattern(mut self, pattern: Option<String>) -> Self {
405        self.pattern = pattern;
406        self
407    }
408
409    /// Returns options with the listing limit updated.
410    ///
411    /// # Panics
412    /// This method does not panic.
413    pub fn with_limit(mut self, limit: usize) -> Self {
414        self.limit = limit;
415        self
416    }
417
418    /// Returns options with the sort key updated.
419    ///
420    /// # Panics
421    /// This method does not panic.
422    pub fn with_sort_by(mut self, sort_by: SortBy) -> Self {
423        self.sort_by = sort_by;
424        self
425    }
426}