diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 14c89c6..fc26928 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2112,6 +2112,15 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -3491,6 +3500,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3538,7 +3561,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -3609,7 +3632,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -3710,7 +3733,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.18", "url", - "windows", + "windows 0.61.3", "zbus", ] @@ -3736,7 +3759,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -3762,7 +3785,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -3929,11 +3952,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4527,10 +4564,10 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", ] [[package]] @@ -4551,7 +4588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.18", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -4563,10 +4600,13 @@ dependencies = [ "jwalk", "serde", "serde_json", + "sysinfo", "tauri", "tauri-build", "tauri-plugin-opener", + "tokio", "walkdir", + "windows-sys 0.59.0", ] [[package]] @@ -4615,6 +4655,16 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -4637,14 +4687,26 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -4656,8 +4718,8 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link 0.2.1", "windows-result 0.4.1", "windows-strings 0.5.1", @@ -4674,6 +4736,17 @@ dependencies = [ "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -4685,6 +4758,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows-interface" version = "0.59.3" @@ -4718,6 +4802,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5155,7 +5248,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 615493e..450d7de 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -25,4 +25,14 @@ serde_json = "1" chrono = "0.4.44" walkdir = "2.5.0" jwalk = "0.8.1" +sysinfo = "0.33.1" +tokio = { version = "1", features = ["full"] } +windows-sys = { version = "0.59", features = [ + "Win32_System_Memory", + "Win32_Foundation", + "Win32_System_ProcessStatus", + "Win32_System_Threading", + "Win32_Security", + "Win32_System_SystemInformation" +] } diff --git a/src-tauri/src/cleaner.rs b/src-tauri/src/cleaner.rs index c4aff4d..2b545f9 100644 --- a/src-tauri/src/cleaner.rs +++ b/src-tauri/src/cleaner.rs @@ -492,3 +492,77 @@ pub async fn run_browser_clean(browser: BrowserType, profile_paths: Vec) 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) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ead23fc..9df53ce 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -60,6 +60,23 @@ async fn start_browser_clean(browser: String, profiles: Vec) -> Result cleaner::MemoryStats { + cleaner::get_memory_stats() +} + +#[tauri::command] +async fn run_memory_clean() -> Result { + cleaner::run_memory_clean().await +} + +#[tauri::command] +async fn run_deep_memory_clean() -> Result { + cleaner::run_deep_memory_clean().await +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -78,7 +95,10 @@ pub fn run() { clean_thumbnails, disable_hibernation, start_browser_scan, - start_browser_clean + start_browser_clean, + get_memory_stats, + run_memory_clean, + run_deep_memory_clean ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/App.vue b/src/App.vue index 1f18634..de6b809 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,6 +23,13 @@ interface FileNode { level: number; isOpen: boolean; isLoading: boolean; } +interface MemoryStats { + total: number; + used: number; + free: number; + percent: number; +} + // --- 状态管理 --- const fastState = ref({ isScanning: false, @@ -33,6 +40,13 @@ const fastState = ref({ cleanResult: null as CleanResult | null, }); +const memoryState = ref({ + stats: null as MemoryStats | null, + isCleaning: false, + lastFreed: "", + isDone: false, +}); + const chromeState = ref({ isScanning: false, isCleaning: false, @@ -349,6 +363,55 @@ function splitSize(sizeStr: string | number) { } return { value: str, unit: '' }; } + +// --- 内存清理逻辑 --- +async function getMemoryStats() { + try { + const stats = await invoke("get_memory_stats"); + memoryState.value.stats = stats; + } catch (err) { + console.error("Failed to fetch memory stats", err); + } +} + +async function startMemoryClean(deep = false) { + if (memoryState.value.isCleaning) return; + memoryState.value.isCleaning = true; + memoryState.value.isDone = false; + + try { + const cmd = deep ? "run_deep_memory_clean" : "run_memory_clean"; + const freedBytes = await invoke(cmd); + memoryState.value.lastFreed = formatItemSize(freedBytes); + memoryState.value.isDone = true; + await getMemoryStats(); + } catch (err) { + showAlert("清理失败", String(err), 'error'); + } finally { + memoryState.value.isCleaning = false; + } +} + +// 自动刷新内存 +import { onMounted, onUnmounted, watch } from "vue"; +let memoryInterval: any = null; + +onMounted(() => { + getMemoryStats(); + memoryInterval = setInterval(() => { + if (activeTab.value === 'clean-memory') { + getMemoryStats(); + } + }, 3000); +}); + +onUnmounted(() => { + if (memoryInterval) clearInterval(memoryInterval); +}); + +watch(activeTab, (newTab) => { + if (newTab === 'clean-memory') getMemoryStats(); +});