use std::path::PathBuf;
use clap::ValueEnum;
use crate::cli::Cli;
use crate::error::{FastSyncError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum CompareMode {
Fast,
Strict,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum VerifyMode {
None,
Changed,
All,
}
impl VerifyMode {
pub fn verify_changed_files(self) -> bool {
match self {
Self::Changed | Self::All => true,
Self::None => false,
}
}
pub fn verify_all_files(self) -> bool {
match self {
Self::All => true,
Self::None | Self::Changed => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum PreserveMode {
Auto,
True,
False,
}
impl PreserveMode {
pub fn enabled(self) -> bool {
match self {
Self::Auto | Self::True => true,
Self::False => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum HashAlgorithm {
Blake3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
pub fn as_str(self) -> &'static str {
match self {
Self::Error => "error",
Self::Warn => "warn",
Self::Info => "info",
Self::Debug => "debug",
Self::Trace => "trace",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum OutputMode {
Text,
Json,
}
#[derive(Debug, Clone)]
pub struct SyncConfig {
pub source: PathBuf,
pub target: PathBuf,
pub dry_run: bool,
pub delete: bool,
pub follow_symlinks: bool,
pub compare_mode: CompareMode,
pub hash_algorithm: HashAlgorithm,
pub verify_mode: VerifyMode,
pub sync_metadata: bool,
pub preserve_times: PreserveMode,
pub preserve_permissions: PreserveMode,
pub atomic_write: bool,
pub threads: usize,
pub queue_size: usize,
pub max_errors: usize,
pub stop_on_error: bool,
pub output: OutputMode,
pub log_level: LogLevel,
}
impl SyncConfig {
pub fn syncs_file_metadata(&self) -> bool {
self.sync_metadata && (self.preserve_times.enabled() || self.preserve_permissions.enabled())
}
}
impl TryFrom<Cli> for SyncConfig {
type Error = FastSyncError;
fn try_from(cli: Cli) -> Result<Self> {
if !cli.source.is_dir() {
return Err(FastSyncError::InvalidSource(cli.source));
}
let threads = match cli.threads.as_deref() {
#[allow(non_snake_case)]
None | Some("auto") => default_threads(),
Some(raw) => raw.parse::<usize>().map_err(|err| FastSyncError::Io {
context: format!("解析 --threads 失败: {raw}"),
source: std::io::Error::new(std::io::ErrorKind::InvalidInput, err),
})?,
}
.max(1);
let queue_size = cli.queue_size.unwrap_or_else(|| threads * 4).max(1);
let compare_mode = if cli.strict {
CompareMode::Strict
} else {
cli.compare
};
Ok(Self {
source: cli.source,
target: cli.target,
dry_run: cli.dry_run,
delete: cli.delete,
follow_symlinks: cli.follow_symlinks,
compare_mode,
hash_algorithm: cli.hash,
verify_mode: cli.verify,
sync_metadata: cli.sync_metadata,
preserve_times: cli.preserve_times,
preserve_permissions: cli.preserve_permissions,
atomic_write: cli.atomic_write,
threads,
queue_size,
max_errors: cli.max_errors,
stop_on_error: cli.stop_on_error,
output: cli.output,
log_level: cli.log_level,
})
}
}
fn default_threads() -> usize {
std::thread::available_parallelism()
.map(|value| value.get())
.unwrap_or(4)
.clamp(1, 8)
}