Files
minmal-file-cloud-email-pim…/clients/desktop/src-tauri/src/sync/watcher.rs
T
Stefan Hacker 50385faa02 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>
2026-04-12 09:50:44 +02:00

60 lines
2.0 KiB
Rust

use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::PathBuf;
use std::sync::mpsc;
pub struct FileWatcher {
_watcher: RecommendedWatcher,
pub receiver: mpsc::Receiver<FileChange>,
pub path: PathBuf,
}
#[derive(Debug, Clone)]
pub struct FileChange {
pub path: PathBuf,
pub kind: ChangeKind,
}
#[derive(Debug, Clone)]
pub enum ChangeKind {
Created,
Modified,
Deleted,
}
impl FileWatcher {
pub fn new(watch_dir: &PathBuf) -> Result<Self, String> {
let (tx, rx) = mpsc::channel();
let mut watcher = RecommendedWatcher::new(
move |result: Result<Event, notify::Error>| {
if let Ok(event) = result {
let kind = match event.kind {
EventKind::Create(_) => Some(ChangeKind::Created),
EventKind::Modify(_) => Some(ChangeKind::Modified),
EventKind::Remove(_) => Some(ChangeKind::Deleted),
_ => None,
};
if let Some(kind) = kind {
for path in event.paths {
// Skip hidden files and temp files
let name = path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
if name.starts_with('.') || name.starts_with('~') || name.ends_with(".tmp") {
continue;
}
let _ = tx.send(FileChange { path, kind: kind.clone() });
}
}
}
},
Config::default(),
).map_err(|e| format!("Watcher-Fehler: {}", e))?;
watcher.watch(watch_dir.as_ref(), RecursiveMode::Recursive)
.map_err(|e| format!("Watch-Fehler: {}", e))?;
Ok(Self { _watcher: watcher, receiver: rx, path: watch_dir.clone() })
}
}