1use clap::Subcommand;
2
3use crate::cli::ui::{highlight, info, success, warning};
4use crate::error::AppError;
5use crate::{
6 get_webdav_sync_settings, set_webdav_sync_settings, webdav_jianguoyun_preset,
7 WebDavSyncService, WebDavSyncSettings,
8};
9
10#[derive(Subcommand, Debug, Clone)]
11pub enum WebDavCommand {
12 Show,
14
15 Set {
17 #[arg(long)]
18 base_url: Option<String>,
19
20 #[arg(long)]
21 remote_root: Option<String>,
22
23 #[arg(long)]
24 profile: Option<String>,
25
26 #[arg(long)]
27 username: Option<String>,
28
29 #[arg(long)]
30 password: Option<String>,
31
32 #[arg(long, conflicts_with = "disable")]
33 enable: bool,
34
35 #[arg(long, conflicts_with = "enable")]
36 disable: bool,
37
38 #[arg(long, conflicts_with = "no_auto_sync")]
39 auto_sync: bool,
40
41 #[arg(long, conflicts_with = "auto_sync")]
42 no_auto_sync: bool,
43 },
44
45 Clear,
47
48 Jianguoyun {
50 #[arg(long)]
51 username: String,
52
53 #[arg(long)]
54 password: String,
55
56 #[arg(long)]
57 remote_root: Option<String>,
58
59 #[arg(long)]
60 profile: Option<String>,
61
62 #[arg(long, conflicts_with = "no_auto_sync")]
63 auto_sync: bool,
64
65 #[arg(long, conflicts_with = "auto_sync")]
66 no_auto_sync: bool,
67 },
68
69 CheckConnection,
71
72 Upload,
74
75 Download,
77
78 MigrateV1ToV2,
80}
81
82pub fn execute(cmd: WebDavCommand) -> Result<(), AppError> {
83 match cmd {
84 WebDavCommand::Show => show(),
85 WebDavCommand::Set {
86 base_url,
87 remote_root,
88 profile,
89 username,
90 password,
91 enable,
92 disable,
93 auto_sync,
94 no_auto_sync,
95 } => set(
96 base_url,
97 remote_root,
98 profile,
99 username,
100 password,
101 enable,
102 disable,
103 auto_sync,
104 no_auto_sync,
105 ),
106 WebDavCommand::Clear => clear(),
107 WebDavCommand::Jianguoyun {
108 username,
109 password,
110 remote_root,
111 profile,
112 auto_sync,
113 no_auto_sync,
114 } => jianguoyun(
115 username,
116 password,
117 remote_root,
118 profile,
119 auto_sync,
120 no_auto_sync,
121 ),
122 WebDavCommand::CheckConnection => check_connection(),
123 WebDavCommand::Upload => upload(),
124 WebDavCommand::Download => download(),
125 WebDavCommand::MigrateV1ToV2 => migrate_v1_to_v2(),
126 }
127}
128
129fn show() -> Result<(), AppError> {
130 let Some(settings) = get_webdav_sync_settings() else {
131 println!(
132 "{}",
133 info(crate::t!(
134 "WebDAV sync is not configured.",
135 "WebDAV 同步尚未配置。"
136 ))
137 );
138 return Ok(());
139 };
140
141 println!("{}", highlight(crate::t!("WebDAV Sync", "WebDAV 同步")));
142 println!("{}", "═".repeat(60));
143 println!("Enabled: {}", yes_no(settings.enabled));
144 println!("Base URL: {}", settings.base_url);
145 println!("Remote Root: {}", settings.remote_root);
146 println!("Profile: {}", settings.profile);
147 println!("Username: {}", blank_as_na(&settings.username));
148 println!("Password: {}", masked_secret(&settings.password));
149 println!("Auto Sync: {}", yes_no(settings.auto_sync));
150 println!(
151 "Last Sync: {}",
152 settings
153 .status
154 .last_sync_at
155 .map(|value| value.to_string())
156 .unwrap_or_else(|| "N/A".to_string())
157 );
158 println!(
159 "Last Error: {}",
160 settings
161 .status
162 .last_error
163 .clone()
164 .filter(|value| !value.trim().is_empty())
165 .unwrap_or_else(|| "N/A".to_string())
166 );
167
168 Ok(())
169}
170
171#[allow(clippy::too_many_arguments)]
172fn set(
173 base_url: Option<String>,
174 remote_root: Option<String>,
175 profile: Option<String>,
176 username: Option<String>,
177 password: Option<String>,
178 enable: bool,
179 disable: bool,
180 auto_sync: bool,
181 no_auto_sync: bool,
182) -> Result<(), AppError> {
183 let mut settings = merged_settings(
184 get_webdav_sync_settings(),
185 base_url,
186 remote_root,
187 profile,
188 username,
189 password,
190 enable,
191 disable,
192 auto_sync,
193 no_auto_sync,
194 );
195 settings.normalize();
196 set_webdav_sync_settings(Some(settings))?;
197 println!(
198 "{}",
199 success(crate::t!(
200 "✓ WebDAV settings saved.",
201 "✓ WebDAV 设置已保存。"
202 ))
203 );
204 Ok(())
205}
206
207fn clear() -> Result<(), AppError> {
208 set_webdav_sync_settings(None)?;
209 println!(
210 "{}",
211 success(crate::t!(
212 "✓ WebDAV settings cleared.",
213 "✓ WebDAV 设置已清空。"
214 ))
215 );
216 Ok(())
217}
218
219fn jianguoyun(
220 username: String,
221 password: String,
222 remote_root: Option<String>,
223 profile: Option<String>,
224 auto_sync: bool,
225 no_auto_sync: bool,
226) -> Result<(), AppError> {
227 let mut settings = webdav_jianguoyun_preset(&username, &password);
228 if let Some(remote_root) = remote_root {
229 settings.remote_root = remote_root;
230 }
231 if let Some(profile) = profile {
232 settings.profile = profile;
233 }
234 if auto_sync {
235 settings.auto_sync = true;
236 }
237 if no_auto_sync {
238 settings.auto_sync = false;
239 }
240 settings.normalize();
241 set_webdav_sync_settings(Some(settings))?;
242 WebDavSyncService::check_connection()?;
243 println!(
244 "{}",
245 success(crate::t!(
246 "✓ Jianguoyun WebDAV preset applied.",
247 "✓ 已应用坚果云 WebDAV 预设。"
248 ))
249 );
250 Ok(())
251}
252
253fn check_connection() -> Result<(), AppError> {
254 WebDavSyncService::check_connection()?;
255 println!(
256 "{}",
257 success(crate::t!(
258 "✓ WebDAV connection succeeded.",
259 "✓ WebDAV 连接成功。"
260 ))
261 );
262 Ok(())
263}
264
265fn upload() -> Result<(), AppError> {
266 let summary = WebDavSyncService::upload()?;
267 println!("{}", success(&summary.message));
268 Ok(())
269}
270
271fn download() -> Result<(), AppError> {
272 let summary = WebDavSyncService::download()?;
273 sync_live_config_after_webdav();
274 println!("{}", success(&summary.message));
275 Ok(())
276}
277
278fn migrate_v1_to_v2() -> Result<(), AppError> {
279 let summary = WebDavSyncService::migrate_v1_to_v2()?;
280 sync_live_config_after_webdav();
281 println!("{}", success(&summary.message));
282 Ok(())
283}
284
285fn sync_live_config_after_webdav() {
286 let Ok(state) = crate::AppState::try_new() else {
287 return;
288 };
289
290 if let Err(err) = crate::services::ProviderService::sync_current_to_live(&state) {
291 let en = format!("Live config sync after WebDAV operation failed: {err}");
292 let zh = format!("WebDAV 操作后同步 live 配置失败: {err}");
293 println!("{}", warning(crate::t!(&en, &zh)));
294 }
295}
296
297#[allow(clippy::too_many_arguments)]
298fn merged_settings(
299 current: Option<WebDavSyncSettings>,
300 base_url: Option<String>,
301 remote_root: Option<String>,
302 profile: Option<String>,
303 username: Option<String>,
304 password: Option<String>,
305 enable: bool,
306 disable: bool,
307 auto_sync: bool,
308 no_auto_sync: bool,
309) -> WebDavSyncSettings {
310 let mut settings = current.unwrap_or_default();
311
312 if let Some(base_url) = base_url {
313 settings.base_url = base_url;
314 }
315 if let Some(remote_root) = remote_root {
316 settings.remote_root = remote_root;
317 }
318 if let Some(profile) = profile {
319 settings.profile = profile;
320 }
321 if let Some(username) = username {
322 settings.username = username;
323 }
324 if let Some(password) = password {
325 settings.password = password;
326 }
327 if enable {
328 settings.enabled = true;
329 }
330 if disable {
331 settings.enabled = false;
332 }
333 if auto_sync {
334 settings.auto_sync = true;
335 }
336 if no_auto_sync {
337 settings.auto_sync = false;
338 }
339
340 settings
341}
342
343fn yes_no(value: bool) -> &'static str {
344 if value {
345 "yes"
346 } else {
347 "no"
348 }
349}
350
351fn blank_as_na(value: &str) -> &str {
352 let trimmed = value.trim();
353 if trimmed.is_empty() {
354 "N/A"
355 } else {
356 trimmed
357 }
358}
359
360fn masked_secret(value: &str) -> String {
361 if value.trim().is_empty() {
362 return "N/A".to_string();
363 }
364 "********".to_string()
365}
366
367#[cfg(test)]
368mod tests {
369 use super::merged_settings;
370 use crate::{WebDavSyncSettings, WebDavSyncStatus};
371
372 #[test]
373 fn merged_settings_updates_selected_fields_only() {
374 let current = WebDavSyncSettings {
375 enabled: true,
376 base_url: "https://dav.example.com/root".to_string(),
377 remote_root: "sync-root".to_string(),
378 profile: "default".to_string(),
379 username: "demo".to_string(),
380 password: "secret".to_string(),
381 auto_sync: false,
382 status: WebDavSyncStatus {
383 last_error: Some("boom".to_string()),
384 ..WebDavSyncStatus::default()
385 },
386 };
387
388 let merged = merged_settings(
389 Some(current),
390 None,
391 Some("next-root".to_string()),
392 None,
393 None,
394 None,
395 false,
396 false,
397 true,
398 false,
399 );
400
401 assert!(merged.enabled);
402 assert_eq!(merged.base_url, "https://dav.example.com/root");
403 assert_eq!(merged.remote_root, "next-root");
404 assert_eq!(merged.profile, "default");
405 assert_eq!(merged.username, "demo");
406 assert_eq!(merged.password, "secret");
407 assert!(merged.auto_sync);
408 assert_eq!(merged.status.last_error.as_deref(), Some("boom"));
409 }
410}