From be121190b31910f3462fa0dd53b72d164752ff9b Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Wed, 15 Apr 2026 09:32:02 +0200 Subject: [PATCH] feat(cloud-files): Mount-Pfad persistieren + Force-Cleanup fuer tote Sync-Roots - cloud_files_mount in AppConfig -> bleibt ueber Neustarts erhalten - Beim Auto-Login wird Cloud-Files automatisch wieder aktiviert - Neue Commands cloud_files_get_mount und cloud_files_force_cleanup - UI zeigt "Aufraeumen"-Button wenn Mount gesetzt aber nicht aktiv, damit User einen Ordner der nach hartem Beenden des Clients als toter Sync-Root haengt wieder freigeben/loeschen kann Co-Authored-By: Claude Opus 4.6 (1M context) --- clients/desktop/src-tauri/src/lib.rs | 36 ++++++++++++++++++- clients/desktop/src-tauri/src/sync/config.rs | 4 +++ clients/desktop/src/App.vue | 38 +++++++++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/clients/desktop/src-tauri/src/lib.rs b/clients/desktop/src-tauri/src/lib.rs index 9bda797..0a0b458 100644 --- a/clients/desktop/src-tauri/src/lib.rs +++ b/clients/desktop/src-tauri/src/lib.rs @@ -949,6 +949,12 @@ async fn cloud_files_enable( *state.cloud_files_loop.lock().unwrap() = Some(handle); *state.cloud_files_watcher.lock().unwrap() = Some(watcher); + + // Mount-Pfad persistieren, damit er beim Neustart wiederkommt. + let mut cfg = AppConfig::load(); + cfg.cloud_files_mount = mount_point.clone(); + let _ = cfg.save(); + Ok(()) } @@ -963,7 +969,33 @@ async fn cloud_files_disable( let _ = handle.tx.send(cloud_files::sync_loop::LoopMessage::Shutdown); } state.cloud_files_watcher.lock().unwrap().take(); - cloud_files::unregister_sync_root(&PathBuf::from(mount_point)) + let result = cloud_files::unregister_sync_root(&PathBuf::from(&mount_point)); + + // Auch bei Fehler Mount aus Config loeschen, damit der Client nicht + // endlos versucht, einen toten Pfad wiederherzustellen. + let mut cfg = AppConfig::load(); + cfg.cloud_files_mount.clear(); + let _ = cfg.save(); + + result +} + +#[tauri::command] +fn cloud_files_get_mount() -> String { + AppConfig::load().cloud_files_mount +} + +/// Notfall-Aufraeumen: Ordner als Sync-Root deregistrieren, auch wenn +/// kein Callback-Handle existiert. Nuetzlich wenn der Client hart beendet +/// wurde und ein "toter" Ordner in Windows haengt. +#[tauri::command] +async fn cloud_files_force_cleanup(mount_point: String) -> Result<(), String> { + let mp = PathBuf::from(&mount_point); + let _ = cloud_files::unregister_sync_root(&mp); + let mut cfg = AppConfig::load(); + cfg.cloud_files_mount.clear(); + let _ = cfg.save(); + Ok(()) } #[tauri::command] @@ -1179,6 +1211,8 @@ pub fn run() { cloud_files_supported, cloud_files_enable, cloud_files_disable, + cloud_files_get_mount, + cloud_files_force_cleanup, cloud_files_pin, cloud_files_unpin, ]) diff --git a/clients/desktop/src-tauri/src/sync/config.rs b/clients/desktop/src-tauri/src/sync/config.rs index 200dead..685f13a 100644 --- a/clients/desktop/src-tauri/src/sync/config.rs +++ b/clients/desktop/src-tauri/src/sync/config.rs @@ -13,6 +13,10 @@ pub struct AppConfig { pub auto_start: bool, #[serde(default)] pub start_minimized: bool, + /// Persistierter Mount-Punkt der Cloud-Files-Integration. + /// Leer = nicht aktiv. Wird beim App-Start wieder aktiviert. + #[serde(default)] + pub cloud_files_mount: String, } impl AppConfig { diff --git a/clients/desktop/src/App.vue b/clients/desktop/src/App.vue index a9faa26..4a9ede1 100644 --- a/clients/desktop/src/App.vue +++ b/clients/desktop/src/App.vue @@ -42,6 +42,27 @@ const cloudFilesError = ref(""); async function checkCloudFilesSupport() { try { cloudFilesSupported.value = await invoke("cloud_files_supported"); } catch { cloudFilesSupported.value = false; } + try { + const saved = await invoke("cloud_files_get_mount"); + if (saved) cloudFilesMountPoint.value = saved; + } catch { /* no saved mount */ } +} + +async function forceCleanupCloudFiles() { + if (!cloudFilesMountPoint.value) return; + if (!confirm(`Sync-Root unter ${cloudFilesMountPoint.value} zwangsweise aufraeumen?\n\nDanach kann der Ordner ggf. geloescht werden.`)) return; + cloudFilesError.value = ""; + cloudFilesBusy.value = true; + try { + await invoke("cloud_files_force_cleanup", { mountPoint: cloudFilesMountPoint.value }); + cloudFilesActive.value = false; + cloudFilesMountPoint.value = ""; + syncLog.value = [`[${ts()}] Cloud-Files Zwangsbereinigung durchgefuehrt`, ...syncLog.value].slice(0, 200); + } catch (err) { + cloudFilesError.value = String(err); + } finally { + cloudFilesBusy.value = false; + } } async function browseCfMount() { @@ -337,7 +358,7 @@ function formatSize(b) { } onMounted(async () => { - checkCloudFilesSupport(); + await checkCloudFilesSupport(); // Try auto-login with saved credentials try { const saved = await invoke("load_saved_config"); @@ -357,6 +378,15 @@ onMounted(async () => { if (syncPaths.value.length > 0) { await startSync(); } + // Cloud-Files automatisch reaktivieren, wenn Mount gespeichert. + if (cloudFilesSupported.value && cloudFilesMountPoint.value) { + try { + await invoke("cloud_files_enable", { mountPoint: cloudFilesMountPoint.value }); + cloudFilesActive.value = true; + } catch (e) { + cloudFilesError.value = `Auto-Reaktivierung fehlgeschlagen: ${e}`; + } + } } catch (err) { syncStatus.value = "Auto-Login fehlgeschlagen"; // Show login screen with pre-filled fields @@ -463,6 +493,12 @@ onUnmounted(() => { unlistenStatus?.(); unlistenLog?.(); unlistenError?.(); unli +
{{ cloudFilesError }}