From 3c340f965394624e6a06960ae3855c48357c9e3e Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Thu, 23 Apr 2026 17:29:25 +0200 Subject: [PATCH] fix(cloud-files): Pin/Unpin tatsaechlich wirksam machen + CLI-Logging set_pin_state hatte drei Probleme: - FILE_READ_ATTRIBUTES: CfSetPinState braucht WRITE_ATTRIBUTES - Kein OPEN_REPARSE_POINT: das Oeffnen selbst hat evtl. die Hydration getriggert, bevor wir unpinnen konnten - Kein CfDehydratePlaceholder: Pin-Wechsel auf UNPINNED aendert nur das Flag, der Disk-Space wird nicht freigegeben Jetzt: - WRITE_ATTRIBUTES + OPEN_REPARSE_POINT beim Handle-Oeffnen - Bei Unpin zusaetzlich CfDehydratePlaceholder, damit "Speicher freigeben" auch wirklich Platz freiraeumt - Ergebnis + Fehler werden nach \.minicloud-cloudfiles.log geschrieben, damit wir sehen was passiert handle_cli_shortcuts loggt nun nach %LOCALAPPDATA%\MiniCloud Sync\ cli.log, weil Explorer die stdout/stderr eines gestarteten Prozesses verwirft. Ohne das Log kann man die vom Kontextmenue gestarteten Aktionen nicht debuggen. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src-tauri/src/cloud_files/windows.rs | 56 ++++++++++++++++--- clients/desktop/src-tauri/src/lib.rs | 35 ++++++++++-- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/clients/desktop/src-tauri/src/cloud_files/windows.rs b/clients/desktop/src-tauri/src/cloud_files/windows.rs index c1144d1..4e16c44 100644 --- a/clients/desktop/src-tauri/src/cloud_files/windows.rs +++ b/clients/desktop/src-tauri/src/cloud_files/windows.rs @@ -509,20 +509,24 @@ fn create_placeholder( pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> { use windows::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, - FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_WRITE_ATTRIBUTES, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE, OPEN_EXISTING, }; let path_wide = U16CString::from_str(file.to_string_lossy().as_ref()) .map_err(|e| e.to_string())?; + // CfSetPinState / CfDehydratePlaceholder brauchen WRITE_ATTRIBUTES. + // OPEN_REPARSE_POINT verhindert, dass das Oeffnen selbst eine + // Hydration ausloest (sonst waere Unpin bedeutungslos). let handle = unsafe { CreateFileW( PCWSTR(path_wide.as_ptr()), - FILE_READ_ATTRIBUTES.0, - FILE_SHARE_READ | FILE_SHARE_WRITE, + (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES).0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, None, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, None, ) } @@ -533,12 +537,50 @@ pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> { } else { CF::CF_PIN_STATE_UNPINNED }; - let res = unsafe { + let set_res = unsafe { 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)) + } else { + None + }; + unsafe { let _ = windows::Win32::Foundation::CloseHandle(handle); } - res.map_err(|e| format!("CfSetPinState: {e}"))?; + + // Log-Verzeichnis ist der Mount-Ordner oder dessen Parent + let log_dir = file + .ancestors() + .find(|p| p.parent().is_some()) + .map(|p| p.to_path_buf()) + .unwrap_or_else(|| file.to_path_buf()); + log_msg( + &log_dir, + &format!( + "set_pin_state file={} pinned={} result={:?} dehydrate_err={:?}", + file.display(), + pinned, + set_res, + dehydrate_err + ), + ); + + set_res.map_err(|e| format!("CfSetPinState: {e}"))?; Ok(()) } diff --git a/clients/desktop/src-tauri/src/lib.rs b/clients/desktop/src-tauri/src/lib.rs index 16760d9..b4a9922 100644 --- a/clients/desktop/src-tauri/src/lib.rs +++ b/clients/desktop/src-tauri/src/lib.rs @@ -1102,24 +1102,49 @@ async fn fetch_remote_entries( /// Short-circuit fuer Shell-Kontextmenue-Aufrufe: /// `minicloud-sync --pin ` oder `--unpin ` fuehrt die /// Aktion direkt aus und beendet. Kein UI, kein Tray. +/// Logs landen in %LOCALAPPDATA%\MiniCloud Sync\cli.log - sonst +/// wuerden wir vom Explorer gestartete Prozesse nie debuggen koennen. #[cfg(windows)] fn handle_cli_shortcuts() { + use std::io::Write; let args: Vec = std::env::args().collect(); if args.len() < 3 { return; } let cmd = args[1].as_str(); + if cmd != "--pin" && cmd != "--unpin" { + return; + } let path = std::path::PathBuf::from(&args[2]); + + let log_path = dirs::data_local_dir() + .unwrap_or_else(|| std::path::PathBuf::from(".")) + .join("MiniCloud Sync") + .join("cli.log"); + if let Some(p) = log_path.parent() { + let _ = std::fs::create_dir_all(p); + } + let log = |msg: &str| { + if let Ok(mut f) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&log_path) + { + let _ = writeln!(f, "[{}] {}", chrono::Utc::now().to_rfc3339(), msg); + } + }; + + log(&format!("CLI invoked: {} {}", cmd, path.display())); let result = match cmd { "--pin" => cloud_files::pin_file(&path), "--unpin" => cloud_files::unpin_file(&path), - _ => return, + _ => unreachable!(), }; - if let Err(e) = result { - eprintln!("[cloud_files CLI] {cmd} failed: {e}"); - std::process::exit(1); + match &result { + Ok(()) => log(&format!("{cmd} OK: {}", path.display())), + Err(e) => log(&format!("{cmd} FAILED: {e}")), } - std::process::exit(0); + std::process::exit(if result.is_ok() { 0 } else { 1 }); } #[cfg(not(windows))]