From dd40c55f7dcf9b72ca0a1f8e10e23c92ae37de76 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Thu, 23 Apr 2026 23:09:28 +0200 Subject: [PATCH] fix(cloud-files): Pin loest Hydration aus, Icon-Refresh via SHChangeNotify CfSetPinState aendert nur das Pin-Flag - ohne expliziten Call passiert am Disk-Inhalt nichts und das Explorer-Icon bleibt unveraendert. Darum klickte "Immer offline verfuegbar" scheinbar ins Leere. - Bei Pin: CfHydratePlaceholder triggert FETCH_DATA und laedt die Datei komplett herunter - Bei Unpin: CfDehydratePlaceholder (war schon da) - Nach jeder Zustandsaenderung SHChangeNotify(SHCNE_UPDATEITEM) damit das Overlay-Icon sofort neu gezeichnet wird, ohne dass der User F5 druecken muss - Log bekommt zusaetzlich hydrate_err fuer Debugging Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src-tauri/src/cloud_files/windows.rs | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/clients/desktop/src-tauri/src/cloud_files/windows.rs b/clients/desktop/src-tauri/src/cloud_files/windows.rs index d397a61..861180c 100644 --- a/clients/desktop/src-tauri/src/cloud_files/windows.rs +++ b/clients/desktop/src-tauri/src/cloud_files/windows.rs @@ -560,29 +560,44 @@ pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> { CF::CfSetPinState(handle, state, CF::CF_SET_PIN_FLAG_NONE, None) }; - // "Speicher freigeben" (unpin): sofort dehydrieren, damit der Platz - // wirklich frei wird. CfSetPinState allein aendert nur den Zustand. - // Der Rueckgabewert wird geloggt aber nicht hart als Fehler gewertet, - // weil die Datei evtl. schon dehydriert ist. - let dehydrate_err = if !pinned && set_res.is_ok() { - let r = unsafe { - CF::CfDehydratePlaceholder( - handle, - 0, - -1, - CF::CF_DEHYDRATE_FLAG_NONE, - None, - ) - }; - r.err().map(|e| format!("{:?}", e)) + // Hydrate bei Pin / Dehydrate bei Unpin. CfSetPinState aendert nur + // das Flag - ohne explizite Hydrate-/Dehydrate-Calls passiert am + // Disk-Inhalt und am Icon nichts Sichtbares. + let (hydrate_err, dehydrate_err) = if set_res.is_ok() { + if pinned { + let r = unsafe { + CF::CfHydratePlaceholder( + handle, + 0, + -1, + CF::CF_HYDRATE_FLAG_NONE, + None, + ) + }; + (r.err().map(|e| format!("{:?}", e)), None) + } else { + let r = unsafe { + CF::CfDehydratePlaceholder( + handle, + 0, + -1, + CF::CF_DEHYDRATE_FLAG_NONE, + None, + ) + }; + (None, r.err().map(|e| format!("{:?}", e))) + } } else { - None + (None, None) }; unsafe { let _ = windows::Win32::Foundation::CloseHandle(handle); } + // Explorer Icon-Overlay aktualisieren + notify_file_update(file); + // Log-Verzeichnis ist der Mount-Ordner oder dessen Parent let log_dir = file .ancestors() @@ -592,10 +607,11 @@ pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> { log_msg( &log_dir, &format!( - "set_pin_state file={} pinned={} result={:?} dehydrate_err={:?}", + "set_pin_state file={} pinned={} result={:?} hydrate_err={:?} dehydrate_err={:?}", file.display(), pinned, set_res, + hydrate_err, dehydrate_err ), ); @@ -603,3 +619,21 @@ pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> { set_res.map_err(|e| format!("CfSetPinState: {e}"))?; Ok(()) } + +/// Sagt dem Shell "diese Datei hat sich geaendert" damit das Overlay- +/// Icon (Wolke/Haken) aktualisiert wird, ohne dass der User F5 druecken +/// muss. +fn notify_file_update(file: &Path) { + use windows::Win32::UI::Shell::{SHChangeNotify, SHCNE_UPDATEITEM, SHCNF_PATHW}; + let Ok(w) = U16CString::from_str(file.to_string_lossy().as_ref()) else { + return; + }; + unsafe { + SHChangeNotify( + SHCNE_UPDATEITEM, + SHCNF_PATHW, + Some(w.as_ptr() as _), + None, + ); + } +}