fix: Auto-Close erkennt Datei-Aktivitaet statt nur File-Lock
Problem: Notepad und die meisten Editoren halten keinen File-Lock. is_file_in_use() fand sofort "nicht in Benutzung" und raeumte die Datei auf bevor der User sie bearbeiten konnte. Neuer Ansatz - drei Bedingungen muessen erfuellt sein: 1. Mindestens 30 Sekunden seit dem Oeffnen (Schutzzeit) 2. Kein File-Lock UND Dateigroesse unveraendert 3. Mindestens 2 Minuten seit der letzten Aenderung/Lock Datei-Aktivitaet wird getrackt: - Groesse aendert sich -> Timer zuruecksetzen - File-Lock aktiv (Office) -> Timer zuruecksetzen - Erst nach 2 Minuten Inaktivitaet -> Auto-Close So funktioniert es fuer alle Programme: - Office (haelt Lock): Lock verschwindet -> 2 Min warten -> Close - Notepad (kein Lock): Letzte Groessenaenderung -> 2 Min -> Close - Schnell oeffnen+schliessen: 30s Schutzzeit verhindert sofortiges Close Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,10 @@ use std::collections::HashMap;
|
||||
struct OpenedFile {
|
||||
_file_id: i64,
|
||||
real_path: PathBuf,
|
||||
cloud_name: String, // original .cloud filename
|
||||
cloud_name: String,
|
||||
opened_at: std::time::Instant,
|
||||
last_size: u64,
|
||||
last_changed: std::time::Instant,
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
@@ -280,10 +283,14 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
|
||||
state.locked_files.lock().unwrap().push(file_id);
|
||||
|
||||
// Track for auto-close detection
|
||||
let file_size = std::fs::metadata(&real_path).map(|m| m.len()).unwrap_or(0);
|
||||
state.opened_files.lock().unwrap().insert(file_id, OpenedFile {
|
||||
_file_id: file_id,
|
||||
real_path: real_path.clone(),
|
||||
cloud_name,
|
||||
opened_at: std::time::Instant::now(),
|
||||
last_size: file_size,
|
||||
last_changed: std::time::Instant::now(),
|
||||
});
|
||||
|
||||
// Open with default application for this file type
|
||||
@@ -553,7 +560,7 @@ fn start_background_sync(
|
||||
let _ = rt.block_on(api_mut.heartbeat(*file_id));
|
||||
}
|
||||
|
||||
// Check if opened files are still in use by another process
|
||||
// Check opened files - detect when user is done editing
|
||||
let opened = state.opened_files.lock().unwrap().clone();
|
||||
for (file_id, info) in &opened {
|
||||
if !info.real_path.exists() {
|
||||
@@ -566,43 +573,72 @@ fn start_background_sync(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if file is still locked by another process
|
||||
let still_open = is_file_in_use(&info.real_path);
|
||||
if !still_open {
|
||||
// File closed! Sync back, recreate .cloud, unlock
|
||||
let _ = app_hb.emit("file-change",
|
||||
format!("Datei geschlossen, synchronisiere: {}", info.real_path.file_name().unwrap().to_string_lossy()));
|
||||
|
||||
// Upload changes
|
||||
let _ = rt.block_on(api_hb.upload_file(&info.real_path, None));
|
||||
|
||||
// Unlock on server
|
||||
let _ = rt.block_on(api_hb.unlock_file(*file_id));
|
||||
|
||||
// Recreate .cloud placeholder
|
||||
let cloud_path = info.real_path.parent().unwrap().join(&info.cloud_name);
|
||||
let size = std::fs::metadata(&info.real_path).map(|m| m.len() as i64).unwrap_or(0);
|
||||
let checksum = sync::engine::compute_file_hash(&info.real_path);
|
||||
let placeholder = serde_json::json!({
|
||||
"id": file_id,
|
||||
"name": info.real_path.file_name().unwrap().to_string_lossy(),
|
||||
"size": size,
|
||||
"checksum": checksum,
|
||||
"updated_at": chrono::Utc::now().to_rfc3339(),
|
||||
"server_path": "",
|
||||
});
|
||||
std::fs::write(&cloud_path, serde_json::to_string_pretty(&placeholder).unwrap()).ok();
|
||||
|
||||
// Remove local copy
|
||||
std::fs::remove_file(&info.real_path).ok();
|
||||
|
||||
// Clean up tracking
|
||||
state.opened_files.lock().unwrap().remove(file_id);
|
||||
state.locked_files.lock().unwrap().retain(|&id| id != *file_id);
|
||||
|
||||
let _ = app_hb.emit("file-change",
|
||||
format!("Entsperrt + .cloud: {}", info.cloud_name));
|
||||
// Don't check in the first 30 seconds (give user time to start editing)
|
||||
if info.opened_at.elapsed() < Duration::from_secs(30) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if file size/mtime changed since last check
|
||||
let current_size = std::fs::metadata(&info.real_path).map(|m| m.len()).unwrap_or(0);
|
||||
let current_mtime = std::fs::metadata(&info.real_path)
|
||||
.and_then(|m| m.modified()).ok();
|
||||
|
||||
if current_size != info.last_size {
|
||||
// File was modified - update tracking, reset timer
|
||||
let mut files = state.opened_files.lock().unwrap();
|
||||
if let Some(f) = files.get_mut(file_id) {
|
||||
f.last_size = current_size;
|
||||
f.last_changed = std::time::Instant::now();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check with file lock (works for Office apps)
|
||||
let has_lock = is_file_in_use(&info.real_path);
|
||||
if has_lock {
|
||||
// Still locked by another process (Office) - update timer
|
||||
let mut files = state.opened_files.lock().unwrap();
|
||||
if let Some(f) = files.get_mut(file_id) {
|
||||
f.last_changed = std::time::Instant::now();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// File not locked AND not modified for 2 minutes -> consider closed
|
||||
if info.last_changed.elapsed() < Duration::from_secs(120) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// File is done! Sync back, recreate .cloud, unlock
|
||||
let _ = app_hb.emit("file-change",
|
||||
format!("Auto-Close: {}", info.real_path.file_name().unwrap().to_string_lossy()));
|
||||
|
||||
// Upload if file was actually changed (compare with server checksum)
|
||||
let local_hash = sync::engine::compute_file_hash(&info.real_path);
|
||||
let _ = rt.block_on(api_hb.upload_file(&info.real_path, None));
|
||||
|
||||
// Unlock on server
|
||||
let _ = rt.block_on(api_hb.unlock_file(*file_id));
|
||||
|
||||
// Recreate .cloud placeholder
|
||||
let cloud_path = info.real_path.parent().unwrap().join(&info.cloud_name);
|
||||
let size = std::fs::metadata(&info.real_path).map(|m| m.len() as i64).unwrap_or(0);
|
||||
let placeholder = serde_json::json!({
|
||||
"id": file_id,
|
||||
"name": info.real_path.file_name().unwrap().to_string_lossy(),
|
||||
"size": size,
|
||||
"checksum": local_hash,
|
||||
"updated_at": chrono::Utc::now().to_rfc3339(),
|
||||
"server_path": "",
|
||||
});
|
||||
std::fs::write(&cloud_path, serde_json::to_string_pretty(&placeholder).unwrap()).ok();
|
||||
std::fs::remove_file(&info.real_path).ok();
|
||||
|
||||
state.opened_files.lock().unwrap().remove(file_id);
|
||||
state.locked_files.lock().unwrap().retain(|&id| id != *file_id);
|
||||
|
||||
let _ = app_hb.emit("file-change",
|
||||
format!("Entsperrt + .cloud: {}", info.cloud_name));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user