feat: File Lock beim Oeffnen + Entsperren per Rechtsklick

Beim Oeffnen einer .cloud-Datei:
- Download + Datei bleibt lokal (wie bisher)
- Lock wird auf dem Server gesetzt (andere sehen "gesperrt von X")
- Kein Auto-Unlock - Datei bleibt gesperrt bis manuell entsperrt

Rechtsklick im Datei-Browser auf Offline-Dateien:
- "Entsperren (Freigeben fuer andere)" - hebt den Lock auf
- "Nicht mehr offline" - .cloud zurueck + automatisch unlock

So bleiben Dateien gesperrt solange man daran arbeitet.
Wenn fertig: Rechtsklick -> Entsperren. Einfach und explizit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-12 03:03:01 +02:00
parent 0845659c84
commit 597dafc461
2 changed files with 46 additions and 2 deletions
+16 -2
View File
@@ -256,11 +256,17 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
}
eprintln!("[OpenCloud] Downloaded {} bytes", std::fs::metadata(&real_path).map(|m| m.len()).unwrap_or(0));
// Remove .cloud placeholder - file stays as real file (like "offline markieren")
// Remove .cloud placeholder - file stays as real file
// Changes will be synced automatically by the file watcher
// User can manually "unmark offline" via right-click to get .cloud back
// User can "unmark offline" or "unlock" via right-click
std::fs::remove_file(&path).ok();
// Lock on server (prevents others from editing)
match api.lock_file(file_id, "Desktop Sync Client").await {
Ok(_) => eprintln!("[OpenCloud] Locked on server"),
Err(e) => eprintln!("[OpenCloud] Lock failed (file may be locked by someone else): {}", e),
}
// Open with default application for this file type
eprintln!("[OpenCloud] Opening with default app: {}", real_path.display());
open::that(&real_path)
@@ -269,6 +275,13 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
Ok(real_path.to_string_lossy().to_string())
}
#[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")?;
api.unlock_file(file_id).await?;
Ok("Datei entsperrt".to_string())
}
#[tauri::command]
async fn get_file_tree(state: State<'_, AppState>) -> Result<serde_json::Value, String> {
let api = state.api.lock().unwrap().clone().ok_or("Nicht eingeloggt")?;
@@ -726,6 +739,7 @@ pub fn run() {
open_cloud_file,
get_file_tree,
get_status,
unlock_file_cmd,
browse_sync_folder,
mark_offline,
unmark_offline,
+30
View File
@@ -84,6 +84,33 @@ async function doMarkOffline(file) {
}
}
async function doUnlockFile(file) {
hideContextMenu();
// Find file ID from server tree
const serverFile = findFileInTree(fileTree.value, file.name);
if (!serverFile) {
syncLog.value = [`[${ts()}] Fehler: Datei nicht auf Server gefunden`, ...syncLog.value];
return;
}
try {
await invoke("unlock_file_cmd", { fileId: serverFile.id });
syncLog.value = [`[${ts()}] Entsperrt: ${file.name}`, ...syncLog.value].slice(0, 200);
} catch (err) {
syncLog.value = [`[${ts()}] Fehler: ${err}`, ...syncLog.value].slice(0, 200);
}
}
function findFileInTree(entries, name) {
for (const e of entries) {
if (e.name === name) return e;
if (e.children) {
const found = findFileInTree(e.children, name);
if (found) return found;
}
}
return null;
}
async function doUnmarkOffline(file) {
hideContextMenu();
try {
@@ -429,6 +456,9 @@ onUnmounted(() => { unlistenStatus?.(); unlistenLog?.(); unlistenError?.(); unli
<div v-if="contextMenu.file?.is_offline" class="cm-item" @click="doUnmarkOffline(contextMenu.file)">
☁ Nicht mehr offline (Platzhalter)
</div>
<div v-if="contextMenu.file?.is_offline" class="cm-item" @click="doUnlockFile(contextMenu.file)">
🔓 Entsperren (Freigeben fuer andere)
</div>
<div class="cm-item" @click="hideContextMenu">Abbrechen</div>
</div>