feat: Echtzeit-Sync via SSE + Journal-basierter 3-Wege-Vergleich
Desktop-Client komplett ueberarbeitet nach Nextcloud-Vorbild: - Persistentes SQLite-Journal (journal.rs) speichert letzten bekannten Stand pro Datei - ueberlebt Client-Neustarts (Hauptbug behoben). - Engine.rs neu: 3-Wege-Vergleich Local <-> Journal <-> Server mit sauberer Konflikt-Kopie (inkl. Username + Zeitstempel). - Loesch-Propagation: Lokal geloeschte Dateien landen im Server- Papierkorb des Owners (auch bei Freigaben). Auf dem Server geloeschte Dateien werden lokal entfernt. - Lock-Flow repariert: frischer Token bei jedem Call, Fehler-Feedback. Echtzeit-Sync: - Backend: SSE-Endpoint /api/sync/events mit In-Memory-Broadcaster. Events bei Create/Update/Delete/Lock/Unlock, Zustellung an Owner plus alle User mit Share-Permission. - Client: persistente SSE-Verbindung mit Auto-Reconnect. Events triggern sofortigen Sync (<100ms). 30s-Polling bleibt als Fallback fuer Netzwerk-Aussetzer. Weitere Fixes: - /api/sync/tree filtert is_trashed=False (Papierkorb wird nicht mehr an Clients gesynct). - Web-GUI: Lock/Unlock-Buttons pro Datei, Admin darf fremde Locks zwangsweise loesen. Rename/Delete disabled bei fremdem Lock. - Lock-Check im Backend bei PUT/DELETE (423 Locked Response). - Background-Sync nur noch einmal pro Prozess gestartet, liest sync_paths pro Iteration neu - add/remove wirkt sofort, kein Client-Neustart mehr noetig. - Watcher werden pro Sync-Pfad individuell verwaltet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -100,15 +100,32 @@
|
||||
:title="(data.has_shares || data.has_permissions) ? 'Freigaben verwalten' : 'Teilen'"
|
||||
@click.stop="openShare(data)"
|
||||
/>
|
||||
<Button
|
||||
v-if="!data.is_folder && !data.locked"
|
||||
icon="pi pi-lock-open"
|
||||
text rounded size="small"
|
||||
title="Auschecken (sperren)"
|
||||
@click.stop="lockFile(data)"
|
||||
/>
|
||||
<Button
|
||||
v-if="!data.is_folder && data.locked && (data.locked_by === auth.user?.username || auth.user?.role === 'admin')"
|
||||
icon="pi pi-lock"
|
||||
text rounded size="small"
|
||||
severity="warn"
|
||||
:title="data.locked_by === auth.user?.username ? 'Einchecken (entsperren)' : 'Lock zwangsweise entfernen (Admin)'"
|
||||
@click.stop="unlockFile(data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-pencil"
|
||||
text rounded size="small"
|
||||
:disabled="data.locked && data.locked_by !== auth.user?.username"
|
||||
@click.stop="openRename(data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
text rounded size="small"
|
||||
severity="danger"
|
||||
:disabled="data.locked && data.locked_by !== auth.user?.username"
|
||||
@click.stop="confirmDelete(data)"
|
||||
/>
|
||||
</div>
|
||||
@@ -659,6 +676,28 @@ async function removeShare(token) {
|
||||
}
|
||||
}
|
||||
|
||||
async function lockFile(data) {
|
||||
try {
|
||||
await apiClient.post(`/api/files/${data.id}/lock`, { client_info: 'Web-GUI' })
|
||||
toast.add({ severity: 'success', summary: 'Ausgecheckt', detail: `${data.name} ist jetzt fuer dich gesperrt.`, life: 3000 })
|
||||
await filesStore.loadFiles(currentParentId())
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Sperren fehlgeschlagen', detail: err.response?.data?.error || err.message, life: 5000 })
|
||||
}
|
||||
}
|
||||
|
||||
async function unlockFile(data) {
|
||||
const isAdminOverride = data.locked_by !== auth.user?.username
|
||||
if (isAdminOverride && !confirm(`Den Lock von ${data.locked_by} zwangsweise entfernen?`)) return
|
||||
try {
|
||||
await apiClient.post(`/api/files/${data.id}/unlock`)
|
||||
toast.add({ severity: 'success', summary: 'Eingecheckt', detail: `${data.name} ist wieder frei.`, life: 3000 })
|
||||
await filesStore.loadFiles(currentParentId())
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Entsperren fehlgeschlagen', detail: err.response?.data?.error || err.message, life: 5000 })
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(data) {
|
||||
deleteTarget.value = data
|
||||
showDeleteConfirm.value = true
|
||||
|
||||
Reference in New Issue
Block a user