feat: Heartbeat fuer Locks - vergessene Locks laufen nach 15 Min ab
Wenn jemand vergisst zu entsperren: - Client laeuft -> Heartbeat alle 60s -> Lock bleibt aktiv - Client geschlossen -> kein Heartbeat -> Lock laeuft nach 15 Min ab - Laptop zugeklappt -> gleicher Effekt -> 15 Min -> frei Tracking: locked_files Vec merkt sich welche Dateien wir gesperrt haben. Heartbeat laeuft im Token-Refresh Thread mit (alle 60s Heartbeat, alle 10 Min Token-Refresh). Lock wird beim Oeffnen getrackt, beim Entsperren/Unmark-Offline entfernt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ struct AppState {
|
|||||||
sync_engine: Mutex<Option<SyncEngine>>,
|
sync_engine: Mutex<Option<SyncEngine>>,
|
||||||
username: Mutex<Option<String>>,
|
username: Mutex<Option<String>>,
|
||||||
watchers: Mutex<Vec<FileWatcher>>,
|
watchers: Mutex<Vec<FileWatcher>>,
|
||||||
|
locked_files: Mutex<Vec<i64>>, // file IDs we have locked on server
|
||||||
sync_running: Arc<Mutex<bool>>,
|
sync_running: Arc<Mutex<bool>>,
|
||||||
sync_paths: Mutex<Vec<SyncPath>>,
|
sync_paths: Mutex<Vec<SyncPath>>,
|
||||||
}
|
}
|
||||||
@@ -263,7 +264,10 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
|
|||||||
|
|
||||||
// Lock on server (prevents others from editing)
|
// Lock on server (prevents others from editing)
|
||||||
match api.lock_file(file_id, "Desktop Sync Client").await {
|
match api.lock_file(file_id, "Desktop Sync Client").await {
|
||||||
Ok(_) => eprintln!("[OpenCloud] Locked on server"),
|
Ok(_) => {
|
||||||
|
eprintln!("[OpenCloud] Locked on server");
|
||||||
|
state.locked_files.lock().unwrap().push(file_id);
|
||||||
|
}
|
||||||
Err(e) => eprintln!("[OpenCloud] Lock failed (file may be locked by someone else): {}", e),
|
Err(e) => eprintln!("[OpenCloud] Lock failed (file may be locked by someone else): {}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +283,7 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
|
|||||||
async fn unlock_file_cmd(state: State<'_, AppState>, file_id: i64) -> Result<String, String> {
|
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")?;
|
let api = state.api.lock().unwrap().clone().ok_or("Nicht eingeloggt")?;
|
||||||
api.unlock_file(file_id).await?;
|
api.unlock_file(file_id).await?;
|
||||||
|
state.locked_files.lock().unwrap().retain(|&id| id != file_id);
|
||||||
Ok("Datei entsperrt".to_string())
|
Ok("Datei entsperrt".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,17 +500,30 @@ fn start_background_sync(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Token refresh every 10 minutes
|
// Token refresh (every 10 min) + Heartbeat for locks (every 60s)
|
||||||
let app_hb = app.clone();
|
let app_hb = app.clone();
|
||||||
let api_hb = api.clone();
|
let api_hb = api.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
let mut api_mut = api_hb.clone();
|
let mut api_mut = api_hb.clone();
|
||||||
|
let mut tick = 0u32;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
std::thread::sleep(Duration::from_secs(600));
|
std::thread::sleep(Duration::from_secs(10));
|
||||||
|
tick += 10;
|
||||||
let state = app_hb.state::<AppState>();
|
let state = app_hb.state::<AppState>();
|
||||||
|
|
||||||
|
// Heartbeat every 60 seconds for locked files
|
||||||
|
if tick % 60 == 0 {
|
||||||
|
let locked = state.locked_files.lock().unwrap().clone();
|
||||||
|
for file_id in &locked {
|
||||||
|
let _ = rt.block_on(api_mut.heartbeat(*file_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token refresh every 10 minutes
|
||||||
|
if tick >= 600 {
|
||||||
|
tick = 0;
|
||||||
if let Ok(new_token) = rt.block_on(api_mut.refresh_token()) {
|
if let Ok(new_token) = rt.block_on(api_mut.refresh_token()) {
|
||||||
if let Some(ref mut api) = *state.api.lock().unwrap() {
|
if let Some(ref mut api) = *state.api.lock().unwrap() {
|
||||||
api.access_token = new_token;
|
api.access_token = new_token;
|
||||||
@@ -513,6 +531,7 @@ fn start_background_sync(
|
|||||||
eprintln!("[Auth] Token refreshed");
|
eprintln!("[Auth] Token refreshed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// File watcher: detects changes and triggers immediate sync
|
// File watcher: detects changes and triggers immediate sync
|
||||||
@@ -630,6 +649,7 @@ pub fn run() {
|
|||||||
username: Mutex::new(None),
|
username: Mutex::new(None),
|
||||||
watchers: Mutex::new(Vec::new()),
|
watchers: Mutex::new(Vec::new()),
|
||||||
sync_running: Arc::new(Mutex::new(false)),
|
sync_running: Arc::new(Mutex::new(false)),
|
||||||
|
locked_files: Mutex::new(Vec::new()),
|
||||||
sync_paths: Mutex::new(Vec::new()),
|
sync_paths: Mutex::new(Vec::new()),
|
||||||
})
|
})
|
||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
|
|||||||
Reference in New Issue
Block a user