From 6c9daa5783f897985d0a0cf1aec585979eef4739 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sun, 12 Apr 2026 11:09:06 +0200 Subject: [PATCH] feat: Offline-Dateien werden beim erneuten Oeffnen wieder ausgecheckt Bisher hat der Client nur beim ersten Oeffnen (.cloud-Platzhalter -> Download) gesperrt. Nach dem Einchecken und erneutem Doppelklick blieb die Datei ungesperrt, weil der Open-Pfad fehlte. Neuer Tauri-Command open_offline_file loest die Server-Datei-ID ueber das Sync-Journal auf, sperrt auf dem Server und oeffnet lokal mit der Standard-App. Im lokalen Dateibrowser: - Doppelklick auf eine bereits offline vorhandene Datei checkt sie nun aus und oeffnet sie (vorher: keine Reaktion) - Rechtsklick-Menue hat "Oeffnen (auschecken)" fuer Offline-Dateien Das Lock triggert wie gehabt notify_file_change -> SSE -> Web-UI aktualisiert den Lock-Status sofort. Co-Authored-By: Claude Opus 4.6 (1M context) --- clients/desktop/src-tauri/src/lib.rs | 47 ++++++++++++++++++++++++++++ clients/desktop/src/App.vue | 15 ++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/clients/desktop/src-tauri/src/lib.rs b/clients/desktop/src-tauri/src/lib.rs index 5af2e0c..9ee67ab 100644 --- a/clients/desktop/src-tauri/src/lib.rs +++ b/clients/desktop/src-tauri/src/lib.rs @@ -319,6 +319,52 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu Ok(real_path.to_string_lossy().to_string()) } +/// Open a real (already-downloaded) file: lock it on the server, then open +/// it with the default application. Used for files that are already offline- +/// available so they still get checked out. +#[tauri::command] +async fn open_offline_file(state: State<'_, AppState>, real_path: String) -> Result { + let path = PathBuf::from(&real_path); + if !path.exists() { + return Err(format!("Datei nicht gefunden: {}", real_path)); + } + + // Resolve file_id by matching this path against the configured sync paths + // and looking the relative path up in the journal. + let (sync_path_id, rel_path) = { + let paths = state.sync_paths.lock().unwrap().clone(); + let mut best: Option<(String, String)> = None; + for sp in &paths { + let base = PathBuf::from(&sp.local_dir); + if let Ok(rel) = path.strip_prefix(&base) { + let rel_str = rel.to_string_lossy().replace('\\', "/"); + best = Some((sp.id.clone(), rel_str)); + break; + } + } + best.ok_or("Datei gehoert zu keinem konfigurierten Sync-Pfad")? + }; + + let journal = state.journal.clone(); + let entry = journal.get(&sync_path_id, &rel_path) + .ok_or("Datei nicht im Sync-Journal - erst einmal synchronisieren")?; + let file_id = entry.file_id.ok_or("Keine Server-ID im Journal")?; + + let api = state.api.lock().unwrap().clone().ok_or("Nicht eingeloggt")?; + match api.lock_file(file_id, "Desktop Sync Client").await { + Ok(_) => { + eprintln!("[OpenOffline] Locked {} on server", rel_path); + let mut locked = state.locked_files.lock().unwrap(); + if !locked.contains(&file_id) { locked.push(file_id); } + } + Err(e) => return Err(format!("Sperre fehlgeschlagen: {}", e)), + } + + open::that(&path) + .map_err(|e| format!("Oeffnen fehlgeschlagen: {}", e))?; + Ok(real_path) +} + #[tauri::command] async fn unlock_file_cmd(state: State<'_, AppState>, file_id: i64) -> Result { let api = state.api.lock().unwrap().clone().ok_or("Nicht eingeloggt")?; @@ -890,6 +936,7 @@ pub fn run() { start_sync, run_sync_now, open_cloud_file, + open_offline_file, get_file_tree, get_status, unlock_file_cmd, diff --git a/clients/desktop/src/App.vue b/clients/desktop/src/App.vue index f035cae..92af2f4 100644 --- a/clients/desktop/src/App.vue +++ b/clients/desktop/src/App.vue @@ -132,6 +132,16 @@ async function doOpenCloudFile(file) { } } +async function doOpenOfflineFile(file) { + hideContextMenu(); + try { + await invoke("open_offline_file", { realPath: file.path }); + syncLog.value = [`[${ts()}] Ausgecheckt + geoeffnet: ${file.name}`, ...syncLog.value].slice(0, 200); + } catch (err) { + syncLog.value = [`[${ts()}] Fehler: ${err}`, ...syncLog.value].slice(0, 200); + } +} + let unlistenStatus, unlistenLog, unlistenError, unlistenFileChange, unlistenTrigger, unlistenCloudOpen; async function handleLogin() { @@ -441,7 +451,7 @@ onUnmounted(() => { unlistenStatus?.(); unlistenLog?.(); unlistenError?.(); unli
{{ f.is_folder ? '📁' : (f.is_cloud ? '☁' : '📄') }} {{ f.name }} @@ -462,6 +472,9 @@ onUnmounted(() => { unlistenStatus?.(); unlistenLog?.(); unlistenError?.(); unli
💾 Offline verfuegbar machen
+
+ 📂 Oeffnen (auschecken) +
☁ Nicht mehr offline (Platzhalter)