diff --git a/src-tauri/src/core/cloud_sync/operator.rs b/src-tauri/src/core/cloud_sync/operator.rs index 574a864d..1729a071 100644 --- a/src-tauri/src/core/cloud_sync/operator.rs +++ b/src-tauri/src/core/cloud_sync/operator.rs @@ -437,12 +437,28 @@ fn storage_timeout_layer() -> TimeoutLayer { } pub(super) async fn ensure_remote_layout(remote: &CloudRemote, base_root: &str) -> AppResult<()> { - remote - .create_dir(&remote_path(base_root, super::remote::SYNC_SNAPSHOTS_DIR)) - .await?; + for path in remote_layout_paths(remote, base_root) { + remote.create_dir(&path).await?; + } Ok(()) } +fn remote_layout_paths(remote: &CloudRemote, base_root: &str) -> Vec { + let snapshots_dir = remote_path(base_root, super::remote::SYNC_SNAPSHOTS_DIR); + let is_webdav = matches!( + remote, + CloudRemote::OpenDal(operator) if operator.info().scheme() == "webdav" + ); + if !is_webdav { + return vec![snapshots_dir]; + } + + snapshots_dir + .match_indices('/') + .map(|(index, _)| snapshots_dir[..=index].to_string()) + .collect() +} + pub(super) fn map_storage_error(error: opendal::Error) -> AppError { let raw = error.to_string(); if let Some(message) = map_webdav_auth_error(&raw) { @@ -1227,6 +1243,59 @@ fn escape_digest_value(value: &str) -> String { mod tests { use super::*; + fn webdav_remote() -> CloudRemote { + let mut settings = CloudSyncSettings::default(); + settings.provider = "webdav".to_string(); + settings.webdav.endpoint = "https://dav.example.com".to_string(); + build_remote(&settings).expect("build WebDAV remote") + } + + #[test] + fn remote_layout_paths_expand_only_for_webdav() { + let webdav = webdav_remote(); + assert_eq!( + remote_layout_paths(&webdav, "nyaterm"), + vec![ + "nyaterm/".to_string(), + "nyaterm/sync/".to_string(), + "nyaterm/sync/snapshots/".to_string(), + ] + ); + + let memory = CloudRemote::Memory(MemoryRemote::default()); + assert_eq!( + remote_layout_paths(&memory, "nyaterm"), + vec!["nyaterm/sync/snapshots/".to_string()] + ); + } + + #[test] + fn webdav_remote_layout_paths_support_empty_and_nested_roots() { + let webdav = webdav_remote(); + + assert_eq!( + remote_layout_paths(&webdav, ""), + vec!["sync/".to_string(), "sync/snapshots/".to_string()] + ); + assert_eq!( + remote_layout_paths(&webdav, "/nyaterm/"), + vec![ + "nyaterm/".to_string(), + "nyaterm/sync/".to_string(), + "nyaterm/sync/snapshots/".to_string(), + ] + ); + assert_eq!( + remote_layout_paths(&webdav, "team/nyaterm"), + vec![ + "team/".to_string(), + "team/nyaterm/".to_string(), + "team/nyaterm/sync/".to_string(), + "team/nyaterm/sync/snapshots/".to_string(), + ] + ); + } + #[test] fn webdav_401_error_reports_generic_auth_hint() { let message = map_webdav_auth_error(