Skip to main content

common/
config.rs

1//! Configuration types for runtime and execution settings
2
3use serde::{Deserialize, Serialize};
4
5/// Dry-run mode for previewing operations without executing them
6#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
7pub enum DryRunMode {
8    /// show only what would be copied/linked/removed
9    #[value(name = "brief")]
10    Brief,
11    /// also show skipped files
12    #[value(name = "all")]
13    All,
14    /// show skipped files with the pattern that caused the skip
15    #[value(name = "explain")]
16    Explain,
17}
18
19/// Runtime configuration for tokio and thread pools
20#[derive(Debug, Clone, Copy, Default)]
21pub struct RuntimeConfig {
22    /// Number of worker threads (0 = number of CPU cores)
23    pub max_workers: usize,
24    /// Number of blocking threads (0 = tokio default of 512)
25    pub max_blocking_threads: usize,
26}
27
28/// Throttling configuration for resource control
29#[derive(Debug, Clone, Copy, Default)]
30pub struct ThrottleConfig {
31    /// Maximum number of open files (None = 80% of system limit)
32    pub max_open_files: Option<usize>,
33    /// Operations per second throttle (0 = no throttle)
34    pub ops_throttle: usize,
35    /// I/O operations per second throttle (0 = no throttle)
36    pub iops_throttle: usize,
37    /// Chunk size for I/O operations (bytes)
38    pub chunk_size: u64,
39}
40
41impl ThrottleConfig {
42    /// Validate configuration and return errors if invalid
43    pub fn validate(&self) -> Result<(), String> {
44        if self.iops_throttle > 0 && self.chunk_size == 0 {
45            return Err("chunk_size must be specified when using iops_throttle".to_string());
46        }
47        Ok(())
48    }
49}
50
51/// Output and logging configuration
52#[derive(Debug, Clone, Copy, Default)]
53pub struct OutputConfig {
54    /// Suppress error output
55    pub quiet: bool,
56    /// Verbosity level: 0=ERROR, 1=INFO, 2=DEBUG, 3=TRACE
57    pub verbose: u8,
58    /// Print summary statistics at the end
59    pub print_summary: bool,
60    /// When true, `run()` will not print text runtime stats after the summary.
61    /// Used when the summary itself includes runtime stats (e.g. JSON format).
62    pub suppress_runtime_stats: bool,
63}
64
65/// Warnings and adjustments for dry-run mode.
66///
67/// When dry-run is active, progress is suppressed (it interferes with stdout
68/// output) and `--summary` is suppressed unless `-v` is also active (verbose
69/// independently enables summary in `common::run()`). This struct collects
70/// warnings about the suppressed flags to print after the operation completes.
71pub struct DryRunWarnings {
72    warnings: Vec<String>,
73}
74impl DryRunWarnings {
75    /// Build dry-run warnings based on which flags were specified.
76    ///
77    /// `has_progress` — whether any progress flags were specified.
78    /// `has_summary` — whether --summary was specified.
79    /// `verbose` — verbosity level; when > 0 summary is printed by `common::run()`
80    ///   regardless of `print_summary`, so we skip the "ignored" warning.
81    /// `has_overwrite` — whether --overwrite was specified (not applicable to rrm).
82    /// `has_filters` — whether --include/--exclude/--filter-file was specified.
83    /// `has_destination` — true for rcp/rlink (copy/link to destination), false for rrm.
84    #[must_use]
85    pub fn new(
86        has_progress: bool,
87        has_summary: bool,
88        verbose: u8,
89        has_overwrite: bool,
90        has_filters: bool,
91        has_destination: bool,
92    ) -> Self {
93        let mut warnings = Vec::new();
94        if has_progress {
95            warnings.push("dry-run: --progress was ignored".to_string());
96        }
97        if has_summary && verbose == 0 {
98            warnings.push("dry-run: --summary was ignored".to_string());
99        }
100        if has_overwrite {
101            warnings.push(
102                "dry-run: --overwrite was ignored; dry-run does not check destination state"
103                    .to_string(),
104            );
105        }
106        if !has_filters {
107            if has_destination {
108                warnings.push(
109                    "dry-run: no filtering specified. dry-run is primarily useful to preview \
110                     --include/--exclude/--filter-file filtering; it does not check whether \
111                     files already exist at the destination."
112                        .to_string(),
113                );
114            } else {
115                warnings.push(
116                    "dry-run: no filtering specified. dry-run is primarily useful to preview \
117                     --include/--exclude/--filter-file filtering."
118                        .to_string(),
119                );
120            }
121        }
122        Self { warnings }
123    }
124    /// Print all collected warnings to stderr.
125    pub fn print(&self) {
126        for warning in &self.warnings {
127            eprintln!("{warning}");
128        }
129    }
130}
131/// Tracing configuration for debugging and profiling
132#[derive(Debug)]
133pub struct TracingConfig {
134    /// Remote tracing layer for distributed tracing
135    pub remote_layer: Option<crate::remote_tracing::RemoteTracingLayer>,
136    /// Debug log file path
137    pub debug_log_file: Option<String>,
138    /// Chrome trace output prefix (produces JSON viewable in Perfetto UI)
139    pub chrome_trace_prefix: Option<String>,
140    /// Flamegraph output prefix (produces folded stacks for inferno)
141    pub flamegraph_prefix: Option<String>,
142    /// Identifier for trace filenames (e.g., "rcp-master", "rcpd-source", "rcpd-destination")
143    pub trace_identifier: String,
144    /// Log level for profiling layers (chrome trace, flamegraph)
145    /// Defaults to "trace" when profiling is enabled
146    pub profile_level: Option<String>,
147    /// Enable tokio-console for live async debugging
148    pub tokio_console: bool,
149    /// Port for tokio-console server (default: 6669)
150    pub tokio_console_port: Option<u16>,
151}
152
153impl Default for TracingConfig {
154    fn default() -> Self {
155        Self {
156            remote_layer: None,
157            debug_log_file: None,
158            chrome_trace_prefix: None,
159            flamegraph_prefix: None,
160            trace_identifier: "unknown".to_string(),
161            profile_level: None,
162            tokio_console: false,
163            tokio_console_port: None,
164        }
165    }
166}