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) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-12 11:09:06 +02:00
parent 88ab3c9b8d
commit 6c9daa5783
2 changed files with 61 additions and 1 deletions
+47
View File
@@ -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<String, String> {
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<String, String> {
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,