use std::fs; use std::path::Path; use std::time::{SystemTime, Duration}; use serde::Serialize; use std::collections::HashMap; use std::sync::Mutex; use std::process::Command; use std::os::windows::process::CommandExt; // 存储全盘扫描后的结果 pub struct DiskState { pub dir_sizes: Mutex>, // pub file_info: Mutex>, } #[derive(Serialize, Clone)] pub struct FileTreeNode { pub name: String, pub path: String, pub is_dir: bool, pub size: u64, pub size_str: String, pub percent: f32, pub file_count: u32, pub has_children: bool, } pub fn format_size(size: u64) -> String { const KB: u64 = 1024; const MB: u64 = KB * 1024; const GB: u64 = MB * 1024; if size >= GB { format!("{:.2} GB", size as f64 / GB as f64) } else if size >= MB { format!("{:.2} MB", size as f64 / MB as f64) } else if size >= KB { format!("{:.2} KB", size as f64 / KB as f64) } else { format!("{} B", size) } } // --- 高级清理功能实现 --- // 1. 系统组件清理 (DISM) pub async fn run_dism_cleanup() -> Result { const CREATE_NO_WINDOW: u32 = 0x08000000; let output = Command::new("dism.exe") .args(&["/online", "/Cleanup-Image", "/StartComponentCleanup"]) .creation_flags(CREATE_NO_WINDOW) .output() .map_err(|e| e.to_string())?; if output.status.success() { Ok("系统组件清理完成。".into()) } else { Err(String::from_utf8_lossy(&output.stderr).to_string()) } } // 2. 清理缩略图缓存 pub async fn clean_thumbnails() -> Result { let local_app_data = std::env::var("LOCALAPPDATA").map_err(|_| "无法获取 LocalAppData 路径")?; let thumb_path = Path::new(&local_app_data).join("Microsoft\\Windows\\Explorer"); let mut count = 0; if thumb_path.exists() { if let Ok(entries) = fs::read_dir(thumb_path) { for entry in entries.filter_map(|e| e.ok()) { let name = entry.file_name().to_string_lossy().to_lowercase(); if name.starts_with("thumbcache_") && name.ends_with(".db") { // 缩略图文件通常被 Explorer 占用,这里尝试删除,失败也继续 if fs::remove_file(entry.path()).is_ok() { count += 1; } } } } } if count > 0 { Ok(format!("成功清理 {} 个缩略图缓存文件。", count)) } else { Ok("未发现可清理的缩略图缓存,或文件正被系统占用。".into()) } } // 3. 关闭休眠文件 pub async fn disable_hibernation() -> Result { const CREATE_NO_WINDOW: u32 = 0x08000000; let output = Command::new("powercfg.exe") .args(&["-h", "off"]) .creation_flags(CREATE_NO_WINDOW) .output() .map_err(|e| e.to_string())?; if output.status.success() { Ok("休眠模式已关闭,hiberfil.sys 已移除。".into()) } else { Err("执行失败,请确保以管理员身份运行。".into()) } } // --- 原有逻辑保持 (磁盘树等) --- #[derive(Serialize, Clone)] pub struct ScanProgress { pub file_count: u64, pub current_path: String, } pub async fn run_full_scan(root_path: String, state: &DiskState, app_handle: tauri::AppHandle) { use jwalk::WalkDir; use tauri::Emitter; let mut dir_sizes = HashMap::new(); let root = Path::new(&root_path); let mut file_count = 0; for entry in WalkDir::new(root).skip_hidden(false).into_iter().filter_map(|e| e.ok()) { if entry.file_type.is_file() { file_count += 1; // 节流推送进度:每 2000 个文件推送一次,避免 IPC 过载 if file_count % 2000 == 0 { let _ = app_handle.emit("scan-progress", ScanProgress { file_count, current_path: entry.parent_path().to_string_lossy().to_string(), }); } let size = entry.metadata().map(|m| m.len()).unwrap_or(0); let mut current_path = entry.parent_path().to_path_buf(); while current_path.starts_with(root) { let path_str = current_path.to_string_lossy().to_string(); *dir_sizes.entry(path_str).or_insert(0) += size; if current_path == root { break; } if let Some(parent) = current_path.parent() { current_path = parent.to_path_buf(); } else { break; } } } } let mut state_dirs = state.dir_sizes.lock().unwrap(); *state_dirs = dir_sizes; } pub fn get_children(parent_path: String, state: &DiskState) -> Vec { let dir_sizes = state.dir_sizes.lock().unwrap(); let mut results = Vec::new(); let parent_size = *dir_sizes.get(&parent_path).unwrap_or(&1); if let Ok(entries) = fs::read_dir(Path::new(&parent_path)) { for entry in entries.filter_map(|e| e.ok()) { let path = entry.path(); let path_str = path.to_string_lossy().to_string(); let is_dir = path.is_dir(); let name = entry.file_name().to_string_lossy().to_string(); let size = if is_dir { *dir_sizes.get(&path_str).unwrap_or(&0) } else { entry.metadata().map(|m| m.len()).unwrap_or(0) }; if size > 0 || !is_dir { results.push(FileTreeNode { name, path: path_str, is_dir, size, size_str: format_size(size), percent: (size as f64 / parent_size as f64 * 100.0) as f32, file_count: 0, has_children: is_dir, }); } } } results.sort_by(|a, b| b.size.cmp(&a.size)); results } pub async fn open_explorer(path: String) -> Result<(), String> { const CREATE_NO_WINDOW: u32 = 0x08000000; // 使用 /select, 参数可以在打开目录的同时选中目标 Command::new("explorer.exe") .arg("/select,") .arg(&path) .creation_flags(CREATE_NO_WINDOW) .spawn() .map_err(|e| e.to_string())?; Ok(()) } // --- 快速模式配置与逻辑 --- #[derive(Clone)] pub struct CleaningConfig { pub name: String, pub path: String, pub filter_days: Option, pub default_enabled: bool, } impl CleaningConfig { fn new(name: &str, path: &str, filter_days: Option, default_enabled: bool) -> Self { Self { name: name.into(), path: path.into(), filter_days, default_enabled } } } /// 获取当前所有快速清理项的配置 fn get_fast_cleaning_configs() -> Vec { let mut configs = Vec::new(); // 1. 用户临时文件 if let Ok(t) = std::env::var("TEMP") { configs.push(CleaningConfig::new("用户临时文件", &t, None, true)); } // 2. 系统临时文件 configs.push(CleaningConfig::new("系统临时文件", "C:\\Windows\\Temp", None, true)); // 3. Windows 更新残留 (通常建议清理 10 天前的) configs.push(CleaningConfig::new("Windows 更新残留", "C:\\Windows\\SoftwareDistribution\\Download", Some(10), true)); // 4. 内核转储文件 configs.push(CleaningConfig::new("内核转储文件", "C:\\Windows\\LiveKernelReports", None, false)); configs } #[derive(Serialize, Clone)] pub struct ScanItem { pub name: String, pub path: String, pub size: u64, pub count: u32, pub enabled: bool, } #[derive(Serialize)] pub struct FastScanResult { pub items: Vec, total_size: String, total_count: u32 } pub async fn run_fast_scan() -> FastScanResult { let configs = get_fast_cleaning_configs(); let mut items = Vec::new(); let mut total_bytes = 0; let mut total_count = 0; for config in configs { let (size, count) = get_dir_stats(Path::new(&config.path), config.filter_days); items.push(ScanItem { name: config.name, path: config.path, size, count, enabled: config.default_enabled, }); total_bytes += size; total_count += count; } FastScanResult { items, total_size: format_size(total_bytes), total_count } } fn get_dir_stats(path: &Path, filter_days: Option) -> (u64, u32) { if !path.exists() { return (0, 0); } let mut size = 0; let mut count = 0; let now = SystemTime::now(); let dur = filter_days.map(|d| Duration::from_secs(d * 24 * 3600)); for entry in walkdir::WalkDir::new(path).into_iter().filter_map(|e| e.ok()) { if entry.file_type().is_file() { let mut ok = true; if let (Some(d), Ok(m)) = (dur, entry.metadata()) { if let Ok(mod_t) = m.modified() { if let Ok(el) = now.duration_since(mod_t) { if el < d { ok = false; } } } } if ok { size += entry.metadata().map(|m| m.len()).unwrap_or(0); count += 1; } } } (size, count) } #[derive(Serialize)] pub struct CleanResult { pub total_freed: String, pub success_count: u32, pub fail_count: u32, } pub async fn run_fast_clean(selected_paths: Vec) -> Result { let configs = get_fast_cleaning_configs(); let mut success_count = 0; let mut fail_count = 0; let mut total_freed: u64 = 0; for config in configs { if selected_paths.contains(&config.path) { let path = Path::new(&config.path); if path.exists() { let (freed, s, f) = clean_directory_contents(path, config.filter_days); total_freed += freed; success_count += s; fail_count += f; } } } Ok(CleanResult { total_freed: format_size(total_freed), success_count, fail_count, }) } fn clean_directory_contents(path: &Path, filter_days: Option) -> (u64, u32, u32) { let mut freed = 0; let mut success = 0; let mut fail = 0; let now = SystemTime::now(); let dur = filter_days.map(|d| Duration::from_secs(d * 24 * 3600)); if let Ok(entries) = fs::read_dir(path) { for entry in entries.filter_map(|e| e.ok()) { let entry_path = entry.path(); let metadata = entry.metadata(); let size = metadata.as_ref().map(|m| m.len()).unwrap_or(0); // 检查过滤逻辑 (如果设置了天数) if let (Some(d), Ok(m)) = (dur, &metadata) { if let Ok(mod_t) = m.modified() { if let Ok(el) = now.duration_since(mod_t) { if el < d { continue; } } } } if entry_path.is_file() { if fs::remove_file(&entry_path).is_ok() { freed += size; success += 1; } else { fail += 1; } } else if entry_path.is_dir() { // 递归清理子目录 let (f, s, fl) = clean_directory_contents(&entry_path, filter_days); freed += f; success += s; fail += fl; // 尝试删除已清空的目录 (如果它本身不是根清理目录且已过期) if fs::remove_dir(&entry_path).is_ok() { success += 1; } else { // 目录可能因为包含未过期的文件而无法删除,这是正常的 } } } } (freed, success, fail) } // --- 浏览器清理逻辑 --- const BROWSER_CACHE_DIRS: &[&str] = &[ "Cache", "Code Cache", "GPUCache", "Media Cache", "Service Worker/CacheStorage", "Service Worker/ScriptCache", "GrShaderCache", "DawnCache", "File System", "blob_storage" ]; #[derive(Serialize, Clone)] pub struct BrowserProfile { pub name: String, pub path_name: String, pub cache_size: u64, pub cache_size_str: String, } #[derive(Serialize)] pub struct BrowserScanResult { pub profiles: Vec, pub total_size: String, } pub enum BrowserType { Chrome, Edge, } impl BrowserType { fn get_user_data_path(&self) -> Result { let local_app_data = std::env::var("LOCALAPPDATA").map_err(|_| "无法获取 LocalAppData 路径")?; let base = std::path::Path::new(&local_app_data); match self { BrowserType::Chrome => Ok(base.join("Google\\Chrome\\User Data")), BrowserType::Edge => Ok(base.join("Microsoft\\Edge\\User Data")), } } } pub async fn run_browser_scan(browser: BrowserType) -> Result { let user_data_path = browser.get_user_data_path()?; let local_state_path = user_data_path.join("Local State"); let mut profiles = Vec::new(); let mut total_bytes = 0; if local_state_path.exists() { let content = fs::read_to_string(local_state_path).map_err(|e| e.to_string())?; let v: serde_json::Value = serde_json::from_str(&content).map_err(|e| e.to_string())?; if let Some(info_cache) = v.get("profile").and_then(|p| p.get("info_cache")).and_then(|i| i.as_object()) { for (dir_name, info) in info_cache { let profile_display_name = info.get("name").and_then(|n| n.as_str()).unwrap_or(dir_name); let profile_path = user_data_path.join(dir_name); if profile_path.exists() { let mut size = 0; // 扫描配置的缓存目录 for sub in BROWSER_CACHE_DIRS { let target = profile_path.join(sub); if target.exists() { size += get_dir_size_simple(&target); } } total_bytes += size; profiles.push(BrowserProfile { name: profile_display_name.to_string(), path_name: dir_name.clone(), cache_size: size, cache_size_str: format_size(size), }); } } } } Ok(BrowserScanResult { profiles, total_size: format_size(total_bytes), }) } fn get_dir_size_simple(path: &std::path::Path) -> u64 { walkdir::WalkDir::new(path) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.file_type().is_file()) .map(|e| e.metadata().map(|m| m.len()).unwrap_or(0)) .sum() } pub async fn run_browser_clean(browser: BrowserType, profile_paths: Vec) -> Result { let user_data_path = browser.get_user_data_path()?; let mut total_freed = 0; let mut success_count = 0; let mut fail_count = 0; for profile_dir in profile_paths { let profile_path = user_data_path.join(&profile_dir); if profile_path.exists() { // 清理配置的缓存目录 for sub in BROWSER_CACHE_DIRS { let target = profile_path.join(sub); if target.exists() { let (f, s, fl) = clean_directory_contents(&target, None); total_freed += f; success_count += s; fail_count += fl; } } } } Ok(CleanResult { total_freed: format_size(total_freed), success_count, fail_count, }) } // --- 内存清理逻辑 --- use sysinfo::{System, ProcessesToUpdate}; #[derive(Serialize, Clone)] pub struct MemoryStats { pub total: u64, pub used: u64, pub free: u64, pub percent: f32, } /// 获取当前系统内存状态 pub fn get_memory_stats() -> MemoryStats { let mut sys = System::new_all(); sys.refresh_memory(); let total = sys.total_memory(); let used = sys.used_memory(); let free = total.saturating_sub(used); let percent = (used as f32 / total as f32) * 100.0; MemoryStats { total, used, free, percent } } /// 执行内存压缩 (Empty Working Set) pub async fn run_memory_clean() -> Result { use windows_sys::Win32::System::ProcessStatus::EmptyWorkingSet; use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_SET_QUOTA}; use windows_sys::Win32::Foundation::CloseHandle; let before = get_memory_stats().used; let mut sys = System::new_all(); sys.refresh_processes(ProcessesToUpdate::All, true); for (pid, _) in sys.processes() { let pid_u32 = pid.as_u32(); unsafe { let handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_SET_QUOTA, 0, pid_u32); if handle != std::ptr::null_mut() { EmptyWorkingSet(handle); CloseHandle(handle); } } } // 给系统一点点时间反应 tokio::time::sleep(std::time::Duration::from_millis(500)).await; let after = get_memory_stats().used; let freed = before.saturating_sub(after); Ok(freed) } /// 深度内存清理 (Standby List / System Cache) pub async fn run_deep_memory_clean() -> Result { use windows_sys::Win32::System::Memory::SetSystemFileCacheSize; let before = get_memory_stats().used; unsafe { // -1 (usize::MAX) 表示清空系统文件缓存 SetSystemFileCacheSize(usize::MAX, usize::MAX, 0); } // 配合普通清理一起执行 let _ = run_memory_clean().await; let after = get_memory_stats().used; let freed = before.saturating_sub(after); Ok(freed) }