From 601e0741b1c1a5a6d5e743acca98026bb47b41a2 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Wed, 15 Apr 2026 09:42:00 +0200 Subject: [PATCH] fix(cloud-files): Platzhalter nicht als lokale Aenderung hochladen + Logging Ursache des "voll gesynct"-Problems: der notify-Watcher feuerte auf die cfapi-Platzhalter, die wir selbst beim Aktivieren angelegt haben. Der sync_loop hat die dann als lokale Aenderung hochgeladen, was implizit die Hydration ausgeloest hat. Ergebnis: keine On-Demand-Platzhalter, sondern voller Sync. - is_cfapi_placeholder() prueft FILE_ATTRIBUTE_OFFLINE / RECALL_ON_DATA_ACCESS / RECALL_ON_OPEN - solche Dateien werden beim Upload uebersprungen - Log-Datei liegt jetzt NEBEN dem Mount (nicht drin), damit sie nicht selbst als Cloud-Datei behandelt wird - FETCH_DATA loggt jetzt auch Success, damit man sieht dass der Callback ueberhaupt feuert Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src-tauri/src/cloud_files/sync_loop.rs | 22 ++++++++ .../src-tauri/src/cloud_files/windows.rs | 53 +++++++++++++++---- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/clients/desktop/src-tauri/src/cloud_files/sync_loop.rs b/clients/desktop/src-tauri/src/cloud_files/sync_loop.rs index 731171e..ee853e9 100644 --- a/clients/desktop/src-tauri/src/cloud_files/sync_loop.rs +++ b/clients/desktop/src-tauri/src/cloud_files/sync_loop.rs @@ -151,6 +151,28 @@ async fn upload_local_change( if !path.is_file() { return Ok(()); } + // cfapi-Platzhalter oder gerade hydrierende Dateien NICHT hochladen - + // sonst wird jede Wolken-Datei sofort komplett gesynct und wir haben + // keinen On-Demand-Modus mehr. + #[cfg(windows)] + { + if super::windows::is_cfapi_placeholder(path) { + super::windows::log_msg( + &cfg.mount_point, + &format!("skip upload (placeholder): {}", path.display()), + ); + return Ok(()); + } + } + // Eigene Log-Datei nicht mit hochladen. + if path + .file_name() + .and_then(|n| n.to_str()) + .map(|n| n.starts_with(".minicloud-")) + .unwrap_or(false) + { + return Ok(()); + } // Relativer Pfad im Mount = Ziel-Pfad auf Server let rel = path .strip_prefix(&cfg.mount_point) diff --git a/clients/desktop/src-tauri/src/cloud_files/windows.rs b/clients/desktop/src-tauri/src/cloud_files/windows.rs index ad6458e..ebf3b8f 100644 --- a/clients/desktop/src-tauri/src/cloud_files/windows.rs +++ b/clients/desktop/src-tauri/src/cloud_files/windows.rs @@ -171,25 +171,60 @@ unsafe extern "system" fn on_fetch_data( // HTTPS-Download im separaten Thread (Callback darf nicht blockieren). let ctx = ctx_snapshot(); std::thread::spawn(move || { - if let Err(e) = transfer_range(connection_key, transfer_key, file_id, offset, length, &ctx) - { - log_err(&ctx.mount_point, &format!( - "fetch file_id={file_id} offset={offset} len={length} FAILED: {e}" - )); - // Garantiert Fehler-Completion, damit Windows nicht in Timeout laeuft. - let _ = complete_transfer(connection_key, transfer_key, None, offset, length); + log_msg(&ctx.mount_point, &format!( + "FETCH_DATA file_id={file_id} offset={offset} len={length}" + )); + match transfer_range(connection_key, transfer_key, file_id, offset, length, &ctx) { + Ok(()) => log_msg(&ctx.mount_point, &format!( + "fetch file_id={file_id} OK" + )), + Err(e) => { + log_err(&ctx.mount_point, &format!( + "fetch file_id={file_id} offset={offset} len={length} FAILED: {e}" + )); + // Garantiert Fehler-Completion, damit Windows nicht in Timeout laeuft. + let _ = complete_transfer(connection_key, transfer_key, None, offset, length); + } } }); } -fn log_err(mount: &Path, msg: &str) { +pub fn log_msg(mount: &Path, msg: &str) { use std::io::Write; - let log = mount.join(".minicloud-cloudfiles.log"); + // Log-Datei NEBEN den Mount, damit sie nicht selbst als Platzhalter + // behandelt wird. + let log = mount + .parent() + .map(|p| p.join(".minicloud-cloudfiles.log")) + .unwrap_or_else(|| PathBuf::from(".minicloud-cloudfiles.log")); if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&log) { let _ = writeln!(f, "[{}] {}", chrono::Utc::now().to_rfc3339(), msg); } } +fn log_err(mount: &Path, msg: &str) { + log_msg(mount, msg); +} + +/// True wenn die Datei ein cfapi-Platzhalter ist (noch nicht hydriert) +/// oder gerade vom Cloud-Filter verwaltet wird. Fuer solche Dateien +/// duerfen wir KEINEN Upload ausloesen, sonst verwandelt der Sync-Loop +/// jeden Platzhalter sofort in eine vollstaendig lokale Datei. +pub fn is_cfapi_placeholder(path: &Path) -> bool { + use windows::Win32::Storage::FileSystem::GetFileAttributesW; + let Ok(w) = U16CString::from_str(path.to_string_lossy().as_ref()) else { + return false; + }; + let attrs = unsafe { GetFileAttributesW(PCWSTR(w.as_ptr())) }; + if attrs == u32::MAX { + return false; + } + // FILE_ATTRIBUTE_OFFLINE (0x1000) oder + // FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS (0x400000) oder + // FILE_ATTRIBUTE_RECALL_ON_OPEN (0x40000) + (attrs & 0x0040_1000) != 0 || (attrs & 0x0004_0000) != 0 +} + fn transfer_range( connection_key: CF::CF_CONNECTION_KEY, transfer_key: i64,