use clap::Subcommand;
use crate::cli::ui::{highlight, info, success, warning};
use crate::error::AppError;
use crate::{
get_webdav_sync_settings, set_webdav_sync_settings, webdav_jianguoyun_preset,
WebDavSyncService, WebDavSyncSettings,
};
#[derive(Subcommand, Debug, Clone)]
pub enum WebDavCommand {
Show,
Set {
#[arg(long)]
base_url: Option<String>,
#[arg(long)]
remote_root: Option<String>,
#[arg(long)]
profile: Option<String>,
#[arg(long)]
username: Option<String>,
#[arg(long)]
password: Option<String>,
#[arg(long, conflicts_with = "disable")]
enable: bool,
#[arg(long, conflicts_with = "enable")]
disable: bool,
#[arg(long, conflicts_with = "no_auto_sync")]
auto_sync: bool,
#[arg(long, conflicts_with = "auto_sync")]
no_auto_sync: bool,
},
Clear,
Jianguoyun {
#[arg(long)]
username: String,
#[arg(long)]
password: String,
#[arg(long)]
remote_root: Option<String>,
#[arg(long)]
profile: Option<String>,
#[arg(long, conflicts_with = "no_auto_sync")]
auto_sync: bool,
#[arg(long, conflicts_with = "auto_sync")]
no_auto_sync: bool,
},
CheckConnection,
Upload,
Download,
MigrateV1ToV2,
}
pub fn execute(cmd: WebDavCommand) -> Result<(), AppError> {
match cmd {
WebDavCommand::Show => show(),
WebDavCommand::Set {
base_url,
remote_root,
profile,
username,
password,
enable,
disable,
auto_sync,
no_auto_sync,
} => set(
base_url,
remote_root,
profile,
username,
password,
enable,
disable,
auto_sync,
no_auto_sync,
),
WebDavCommand::Clear => clear(),
WebDavCommand::Jianguoyun {
username,
password,
remote_root,
profile,
auto_sync,
no_auto_sync,
} => jianguoyun(
username,
password,
remote_root,
profile,
auto_sync,
no_auto_sync,
),
WebDavCommand::CheckConnection => check_connection(),
WebDavCommand::Upload => upload(),
WebDavCommand::Download => download(),
WebDavCommand::MigrateV1ToV2 => migrate_v1_to_v2(),
}
}
fn show() -> Result<(), AppError> {
let Some(settings) = get_webdav_sync_settings() else {
println!(
"{}",
info(crate::t!(
"WebDAV sync is not configured.",
"WebDAV 同步尚未配置。"
))
);
return Ok(());
};
println!("{}", highlight(crate::t!("WebDAV Sync", "WebDAV 同步")));
println!("{}", "═".repeat(60));
println!("Enabled: {}", yes_no(settings.enabled));
println!("Base URL: {}", settings.base_url);
println!("Remote Root: {}", settings.remote_root);
println!("Profile: {}", settings.profile);
println!("Username: {}", blank_as_na(&settings.username));
println!("Password: {}", masked_secret(&settings.password));
println!("Auto Sync: {}", yes_no(settings.auto_sync));
println!(
"Last Sync: {}",
settings
.status
.last_sync_at
.map(|value| value.to_string())
.unwrap_or_else(|| "N/A".to_string())
);
println!(
"Last Error: {}",
settings
.status
.last_error
.clone()
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "N/A".to_string())
);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn set(
base_url: Option<String>,
remote_root: Option<String>,
profile: Option<String>,
username: Option<String>,
password: Option<String>,
enable: bool,
disable: bool,
auto_sync: bool,
no_auto_sync: bool,
) -> Result<(), AppError> {
let mut settings = merged_settings(
get_webdav_sync_settings(),
base_url,
remote_root,
profile,
username,
password,
enable,
disable,
auto_sync,
no_auto_sync,
);
settings.normalize();
set_webdav_sync_settings(Some(settings))?;
println!(
"{}",
success(crate::t!(
"✓ WebDAV settings saved.",
"✓ WebDAV 设置已保存。"
))
);
Ok(())
}
fn clear() -> Result<(), AppError> {
set_webdav_sync_settings(None)?;
println!(
"{}",
success(crate::t!(
"✓ WebDAV settings cleared.",
"✓ WebDAV 设置已清空。"
))
);
Ok(())
}
fn jianguoyun(
username: String,
password: String,
remote_root: Option<String>,
profile: Option<String>,
auto_sync: bool,
no_auto_sync: bool,
) -> Result<(), AppError> {
let mut settings = webdav_jianguoyun_preset(&username, &password);
if let Some(remote_root) = remote_root {
settings.remote_root = remote_root;
}
if let Some(profile) = profile {
settings.profile = profile;
}
if auto_sync {
settings.auto_sync = true;
}
if no_auto_sync {
settings.auto_sync = false;
}
settings.normalize();
set_webdav_sync_settings(Some(settings))?;
WebDavSyncService::check_connection()?;
println!(
"{}",
success(crate::t!(
"✓ Jianguoyun WebDAV preset applied.",
"✓ 已应用坚果云 WebDAV 预设。"
))
);
Ok(())
}
fn check_connection() -> Result<(), AppError> {
WebDavSyncService::check_connection()?;
println!(
"{}",
success(crate::t!(
"✓ WebDAV connection succeeded.",
"✓ WebDAV 连接成功。"
))
);
Ok(())
}
fn upload() -> Result<(), AppError> {
let summary = WebDavSyncService::upload()?;
println!("{}", success(&summary.message));
Ok(())
}
fn download() -> Result<(), AppError> {
let summary = WebDavSyncService::download()?;
sync_live_config_after_webdav();
println!("{}", success(&summary.message));
Ok(())
}
fn migrate_v1_to_v2() -> Result<(), AppError> {
let summary = WebDavSyncService::migrate_v1_to_v2()?;
sync_live_config_after_webdav();
println!("{}", success(&summary.message));
Ok(())
}
fn sync_live_config_after_webdav() {
let Ok(state) = crate::AppState::try_new() else {
return;
};
if let Err(err) = crate::services::ProviderService::sync_current_to_live(&state) {
let en = format!("Live config sync after WebDAV operation failed: {err}");
let zh = format!("WebDAV 操作后同步 live 配置失败: {err}");
println!("{}", warning(crate::t!(&en, &zh)));
}
}
#[allow(clippy::too_many_arguments)]
fn merged_settings(
current: Option<WebDavSyncSettings>,
base_url: Option<String>,
remote_root: Option<String>,
profile: Option<String>,
username: Option<String>,
password: Option<String>,
enable: bool,
disable: bool,
auto_sync: bool,
no_auto_sync: bool,
) -> WebDavSyncSettings {
let mut settings = current.unwrap_or_default();
if let Some(base_url) = base_url {
settings.base_url = base_url;
}
if let Some(remote_root) = remote_root {
settings.remote_root = remote_root;
}
if let Some(profile) = profile {
settings.profile = profile;
}
if let Some(username) = username {
settings.username = username;
}
if let Some(password) = password {
settings.password = password;
}
if enable {
settings.enabled = true;
}
if disable {
settings.enabled = false;
}
if auto_sync {
settings.auto_sync = true;
}
if no_auto_sync {
settings.auto_sync = false;
}
settings
}
fn yes_no(value: bool) -> &'static str {
if value {
"yes"
} else {
"no"
}
}
fn blank_as_na(value: &str) -> &str {
let trimmed = value.trim();
if trimmed.is_empty() {
"N/A"
} else {
trimmed
}
}
fn masked_secret(value: &str) -> String {
if value.trim().is_empty() {
return "N/A".to_string();
}
"********".to_string()
}
#[cfg(test)]
mod tests {
use super::merged_settings;
use crate::{WebDavSyncSettings, WebDavSyncStatus};
#[test]
fn merged_settings_updates_selected_fields_only() {
let current = WebDavSyncSettings {
enabled: true,
base_url: "https://dav.example.com/root".to_string(),
remote_root: "sync-root".to_string(),
profile: "default".to_string(),
username: "demo".to_string(),
password: "secret".to_string(),
auto_sync: false,
status: WebDavSyncStatus {
last_error: Some("boom".to_string()),
..WebDavSyncStatus::default()
},
};
let merged = merged_settings(
Some(current),
None,
Some("next-root".to_string()),
None,
None,
None,
false,
false,
true,
false,
);
assert!(merged.enabled);
assert_eq!(merged.base_url, "https://dav.example.com/root");
assert_eq!(merged.remote_root, "next-root");
assert_eq!(merged.profile, "default");
assert_eq!(merged.username, "demo");
assert_eq!(merged.password, "secret");
assert!(merged.auto_sync);
assert_eq!(merged.status.last_error.as_deref(), Some("boom"));
}
}