diff --git a/src-tauri/src/backend/browser_clean.rs b/src-tauri/src/backend/browser_clean.rs index 8375ce4..749fa01 100644 --- a/src-tauri/src/backend/browser_clean.rs +++ b/src-tauri/src/backend/browser_clean.rs @@ -2,8 +2,11 @@ use std::fs; use std::path::{Path, PathBuf}; use crate::backend::fast_clean::clean_directory_contents; -use crate::backend::models::{BrowserProfile, BrowserScanResult, BrowserType, CleanResult}; +use crate::backend::models::{ + BrowserProfile, BrowserScanResult, BrowserType, CleanResult, ProjectCleanProgress, +}; use crate::backend::utils::{format_size, get_dir_size_simple}; +use tauri::Emitter; const BROWSER_CACHE_DIRS: &[&str] = &[ "Cache", @@ -80,18 +83,23 @@ pub async fn run_browser_scan(browser: BrowserType) -> Result, + app_handle: tauri::AppHandle, ) -> 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; + let mut approx_completed_bytes = 0; + let total_items = profile_paths.len() as u32; - for profile_dir in profile_paths { + for (index, profile_dir) in profile_paths.into_iter().enumerate() { let profile_path = user_data_path.join(&profile_dir); + let mut profile_estimated_size = 0; if profile_path.exists() { for sub_dir in BROWSER_CACHE_DIRS { let target = profile_path.join(sub_dir); if target.exists() { + profile_estimated_size += get_dir_size_simple(&target); let (freed, success, fail) = clean_directory_contents(&target, None); total_freed += freed; success_count += success; @@ -99,6 +107,17 @@ pub async fn run_browser_clean( } } } + + approx_completed_bytes += profile_estimated_size; + let _ = app_handle.emit( + "browser-clean-progress", + ProjectCleanProgress { + completed_items: (index + 1) as u32, + total_items, + current_item: profile_dir, + approx_completed_bytes, + }, + ); } Ok(CleanResult { diff --git a/src-tauri/src/backend/fast_clean.rs b/src-tauri/src/backend/fast_clean.rs index a706cf5..3144bb8 100644 --- a/src-tauri/src/backend/fast_clean.rs +++ b/src-tauri/src/backend/fast_clean.rs @@ -2,7 +2,9 @@ use std::fs; use std::path::Path; use std::time::{Duration, SystemTime}; -use crate::backend::models::{CleanResult, CleaningConfig, FastScanResult, ScanItem}; +use tauri::Emitter; + +use crate::backend::models::{CleanResult, CleaningConfig, FastScanResult, ProjectCleanProgress, ScanItem}; use crate::backend::utils::format_size; fn get_fast_cleaning_configs() -> Vec { @@ -89,22 +91,42 @@ fn get_dir_stats(path: &Path, filter_days: Option) -> (u64, u32) { (size, count) } -pub async fn run_fast_clean(selected_paths: Vec) -> Result { - let configs = get_fast_cleaning_configs(); +pub async fn run_fast_clean( + selected_paths: Vec, + app_handle: tauri::AppHandle, +) -> Result { + let selected_configs: Vec = get_fast_cleaning_configs() + .into_iter() + .filter(|config| selected_paths.contains(&config.path)) + .collect(); + let mut success_count = 0; let mut fail_count = 0; let mut total_freed = 0; + let mut approx_completed_bytes = 0; + let total_items = selected_configs.len() as u32; - for config in configs { - if selected_paths.contains(&config.path) { - let path = Path::new(&config.path); - if path.exists() { - let (freed, success, fail) = clean_directory_contents(path, config.filter_days); - total_freed += freed; - success_count += success; - fail_count += fail; - } + for (index, config) in selected_configs.into_iter().enumerate() { + let path = Path::new(&config.path); + let item_size = get_dir_stats(path, config.filter_days).0; + + if path.exists() { + let (freed, success, fail) = clean_directory_contents(path, config.filter_days); + total_freed += freed; + success_count += success; + fail_count += fail; } + + approx_completed_bytes += item_size; + let _ = app_handle.emit( + "fast-clean-progress", + ProjectCleanProgress { + completed_items: (index + 1) as u32, + total_items, + current_item: config.name, + approx_completed_bytes, + }, + ); } Ok(CleanResult { diff --git a/src-tauri/src/backend/models.rs b/src-tauri/src/backend/models.rs index f49492c..a97d54f 100644 --- a/src-tauri/src/backend/models.rs +++ b/src-tauri/src/backend/models.rs @@ -18,6 +18,14 @@ pub struct ScanProgress { pub current_path: String, } +#[derive(Serialize, Clone)] +pub struct ProjectCleanProgress { + pub completed_items: u32, + pub total_items: u32, + pub current_item: String, + pub approx_completed_bytes: u64, +} + #[derive(Clone)] pub struct CleaningConfig { pub name: String, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0767e97..291f4a8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,8 +10,11 @@ async fn start_fast_scan() -> backend::models::FastScanResult { } #[tauri::command] -async fn start_fast_clean(selected_paths: Vec) -> Result { - backend::fast_clean::run_fast_clean(selected_paths).await +async fn start_fast_clean( + selected_paths: Vec, + app_handle: tauri::AppHandle, +) -> Result { + backend::fast_clean::run_fast_clean(selected_paths, app_handle).await } #[tauri::command] @@ -66,6 +69,7 @@ async fn start_browser_scan(browser: String) -> Result, + app_handle: tauri::AppHandle, ) -> Result { let browser_type = if browser == "chrome" { backend::models::BrowserType::Chrome @@ -73,7 +77,7 @@ async fn start_browser_clean( backend::models::BrowserType::Edge }; - backend::browser_clean::run_browser_clean(browser_type, profiles).await + backend::browser_clean::run_browser_clean(browser_type, profiles, app_handle).await } #[tauri::command] diff --git a/src/composables/useBrowserClean.ts b/src/composables/useBrowserClean.ts index bea8237..9a60cf4 100644 --- a/src/composables/useBrowserClean.ts +++ b/src/composables/useBrowserClean.ts @@ -2,12 +2,14 @@ import { computed, ref } from "vue"; import { startBrowserClean as runBrowserCleanCommand, startBrowserScan as runBrowserScanCommand, + subscribeBrowserCleanProgress, } from "../services/tauri/cleaner"; import type { AlertOptions, BrowserScanResult, CleanResult, ConfirmOptions, + ProjectCleanProgressPayload, } from "../types/cleaner"; import { formatItemSize } from "../utils/format"; @@ -19,6 +21,13 @@ interface BrowserState { cleanResult: CleanResult | null; } +interface BrowserCleanProgressState { + completedItems: number; + totalItems: number; + currentItem: string; + approxCompletedBytes: number; +} + export function useBrowserClean( browser: "chrome" | "edge", showAlert: (options: AlertOptions) => void, @@ -31,20 +40,50 @@ export function useBrowserClean( scanResult: null, cleanResult: null, }); + const cleanProgress = ref({ + completedItems: 0, + totalItems: 0, + currentItem: "", + approxCompletedBytes: 0, + }); const selectedStats = computed(() => { const scanResult = state.value.scanResult; - if (!scanResult) return { sizeStr: "0 B", count: 0, hasSelection: false }; + if (!scanResult) return { totalBytes: 0, sizeStr: "0 B", count: 0, hasSelection: false }; const enabledProfiles = scanResult.profiles.filter((profile) => profile.enabled); const totalBytes = enabledProfiles.reduce((acc, profile) => acc + profile.cache_size, 0); return { + totalBytes, sizeStr: formatItemSize(totalBytes), count: enabledProfiles.length, hasSelection: enabledProfiles.length > 0, }; }); + const cleanProgressSizeStr = computed(() => formatSizeValue(cleanProgress.value.approxCompletedBytes)); + + function formatSizeValue(bytes: number) { + return formatItemSize(bytes); + } + + function resetCleanProgress() { + cleanProgress.value = { + completedItems: 0, + totalItems: 0, + currentItem: "", + approxCompletedBytes: 0, + }; + } + + function handleCleanProgress(payload: ProjectCleanProgressPayload) { + cleanProgress.value = { + completedItems: payload.completed_items, + totalItems: payload.total_items, + currentItem: payload.current_item, + approxCompletedBytes: payload.approx_completed_bytes, + }; + } async function startScan() { const current = state.value; @@ -102,7 +141,9 @@ export function useBrowserClean( return; } + resetCleanProgress(); current.isCleaning = true; + const unlisten = await subscribeBrowserCleanProgress(handleCleanProgress); try { current.cleanResult = await runBrowserCleanCommand(browser, selectedProfiles); current.isDone = true; @@ -114,6 +155,7 @@ export function useBrowserClean( type: "error", }); } finally { + unlisten(); current.isCleaning = false; } } @@ -138,11 +180,14 @@ export function useBrowserClean( scanResult: null, cleanResult: null, }; + resetCleanProgress(); } return { state, selectedStats, + cleanProgress, + cleanProgressSizeStr, startScan, startClean, toggleAllProfiles, diff --git a/src/composables/useFastClean.ts b/src/composables/useFastClean.ts index cf673e0..a03b27b 100644 --- a/src/composables/useFastClean.ts +++ b/src/composables/useFastClean.ts @@ -1,10 +1,15 @@ import { computed, ref } from "vue"; -import { startFastClean as runFastCleanCommand, startFastScan as runFastScanCommand } from "../services/tauri/cleaner"; +import { + startFastClean as runFastCleanCommand, + startFastScan as runFastScanCommand, + subscribeFastCleanProgress, +} from "../services/tauri/cleaner"; import type { AlertOptions, CleanResult, ConfirmOptions, FastScanResult, + ProjectCleanProgressPayload, } from "../types/cleaner"; import { formatItemSize } from "../utils/format"; @@ -17,6 +22,13 @@ interface FastState { cleanResult: CleanResult | null; } +interface FastCleanProgressState { + completedItems: number; + totalItems: number; + currentItem: string; + approxCompletedBytes: number; +} + export function useFastClean( showAlert: (options: AlertOptions) => void, requestConfirm: (options: ConfirmOptions) => Promise, @@ -29,21 +41,47 @@ export function useFastClean( scanResult: null, cleanResult: null, }); + const cleanProgress = ref({ + completedItems: 0, + totalItems: 0, + currentItem: "", + approxCompletedBytes: 0, + }); const selectedStats = computed(() => { const scanResult = state.value.scanResult; - if (!scanResult) return { sizeStr: "0 B", count: 0, hasSelection: false }; + if (!scanResult) return { totalBytes: 0, sizeStr: "0 B", count: 0, hasSelection: false }; const enabledItems = scanResult.items.filter((item) => item.enabled); const totalBytes = enabledItems.reduce((acc, item) => acc + item.size, 0); const totalCount = enabledItems.reduce((acc, item) => acc + item.count, 0); return { + totalBytes, sizeStr: formatItemSize(totalBytes), count: totalCount, hasSelection: enabledItems.length > 0, }; }); + const cleanProgressSizeStr = computed(() => formatItemSize(cleanProgress.value.approxCompletedBytes)); + + function resetCleanProgress() { + cleanProgress.value = { + completedItems: 0, + totalItems: 0, + currentItem: "", + approxCompletedBytes: 0, + }; + } + + function handleCleanProgress(payload: ProjectCleanProgressPayload) { + cleanProgress.value = { + completedItems: payload.completed_items, + totalItems: payload.total_items, + currentItem: payload.current_item, + approxCompletedBytes: payload.approx_completed_bytes, + }; + } async function startScan() { const current = state.value; @@ -104,7 +142,9 @@ export function useFastClean( } } + resetCleanProgress(); current.isCleaning = true; + const unlisten = await subscribeFastCleanProgress(handleCleanProgress); try { current.cleanResult = await runFastCleanCommand(selectedPaths); current.isDone = true; @@ -116,6 +156,7 @@ export function useFastClean( type: "error", }); } finally { + unlisten(); current.isCleaning = false; } } @@ -129,11 +170,14 @@ export function useFastClean( scanResult: null, cleanResult: null, }; + resetCleanProgress(); } return { state, selectedStats, + cleanProgress, + cleanProgressSizeStr, startScan, startClean, reset, diff --git a/src/pages/BrowserCleanPage.vue b/src/pages/BrowserCleanPage.vue index 0fa1a74..acdbd8f 100644 --- a/src/pages/BrowserCleanPage.vue +++ b/src/pages/BrowserCleanPage.vue @@ -9,7 +9,7 @@ const props = defineProps<{ requestConfirm: (options: ConfirmOptions) => Promise; }>(); -const { state, selectedStats, startScan, startClean, toggleAllProfiles, invertProfiles, reset } = +const { state, selectedStats, cleanProgress, cleanProgressSizeStr, startScan, startClean, toggleAllProfiles, invertProfiles, reset } = useBrowserClean(props.browser, props.showAlert, props.requestConfirm); const browserName = props.browser === "chrome" ? "谷歌浏览器" : "微软浏览器"; @@ -36,23 +36,26 @@ const browserName = props.browser === "chrome" ? "谷歌浏览器" : "微软浏
- 🌍 -

扫描完成

+ {{ state.isCleaning ? "🧹" : "🌍" }} +

{{ state.isCleaning ? "正在清理" : "扫描完成" }}

- {{ splitSize(selectedStats.sizeStr).value }} - {{ splitSize(selectedStats.sizeStr).unit }} + {{ splitSize(state.isCleaning ? cleanProgressSizeStr : selectedStats.sizeStr).value }} + {{ splitSize(state.isCleaning ? cleanProgressSizeStr : selectedStats.sizeStr).unit }} - 预计释放 + {{ state.isCleaning ? "已处理约" : "预计释放" }}
- {{ selectedStats.count }} - 用户资料数量 + {{ state.isCleaning ? `${cleanProgress.completedItems}/${cleanProgress.totalItems}` : selectedStats.count }} + {{ state.isCleaning ? "已完成资料" : "用户资料数量" }}
+

+ 正在清理:{{ cleanProgress.currentItem || "准备开始..." }},建议保持浏览器关闭以减少文件占用。 +

@@ -86,7 +89,7 @@ const browserName = props.browser === "chrome" ? "谷歌浏览器" : "微软浏
-
+

用户资料列表

diff --git a/src/pages/FastCleanPage.vue b/src/pages/FastCleanPage.vue index 7ff1326..e0d1d88 100644 --- a/src/pages/FastCleanPage.vue +++ b/src/pages/FastCleanPage.vue @@ -8,7 +8,7 @@ const props = defineProps<{ requestConfirm: (options: ConfirmOptions) => Promise; }>(); -const { state, selectedStats, startScan, startClean, reset } = useFastClean( +const { state, selectedStats, cleanProgress, cleanProgressSizeStr, startScan, startClean, reset } = useFastClean( props.showAlert, props.requestConfirm, ); @@ -35,24 +35,28 @@ const { state, selectedStats, startScan, startClean, reset } = useFastClean(
- 📋 -

扫描完成

+ {{ state.isCleaning ? "🧹" : "📋" }} +

{{ state.isCleaning ? "正在清理" : "扫描完成" }}

- {{ splitSize(selectedStats.sizeStr).value }} - {{ splitSize(selectedStats.sizeStr).unit }} + {{ splitSize(state.isCleaning ? cleanProgressSizeStr : selectedStats.sizeStr).value }} + {{ splitSize(state.isCleaning ? cleanProgressSizeStr : selectedStats.sizeStr).unit }} - 预计释放 + {{ state.isCleaning ? "已处理约" : "预计释放" }}
- {{ selectedStats.count }} - 文件数量 + {{ state.isCleaning ? `${cleanProgress.completedItems}/${cleanProgress.totalItems}` : selectedStats.count }} + {{ state.isCleaning ? "已完成项目" : "文件数量" }}
+

+ 正在清理:{{ cleanProgress.currentItem || "准备开始..." }},请稍候,不要关闭程序。 +

+ @@ -87,7 +91,7 @@ const { state, selectedStats, startScan, startClean, reset } = useFastClean(
-
+

清理项详情

void, +) { + return listen("fast-clean-progress", (event) => { + handler(event.payload); + }); +} + +export function subscribeBrowserCleanProgress( + handler: (payload: ProjectCleanProgressPayload) => void, +) { + return listen("browser-clean-progress", (event) => { + handler(event.payload); + }); +} + export function openInExplorer(path: string) { return invoke("open_in_explorer", { path }); } diff --git a/src/styles/common.css b/src/styles/common.css index 9e08442..fe6a7e6 100644 --- a/src/styles/common.css +++ b/src/styles/common.css @@ -136,6 +136,13 @@ width: 180px; } +.cleaning-note { + margin: -8px 0 24px; + font-size: 13px; + line-height: 1.6; + color: var(--text-sec); +} + .scan-circle-container { width: 200px; height: 200px; diff --git a/src/types/cleaner.ts b/src/types/cleaner.ts index 533ebe1..60f9748 100644 --- a/src/types/cleaner.ts +++ b/src/types/cleaner.ts @@ -64,6 +64,13 @@ export interface ScanProgressPayload { current_path: string; } +export interface ProjectCleanProgressPayload { + completed_items: number; + total_items: number; + current_item: string; + approx_completed_bytes: number; +} + export type ModalType = "info" | "success" | "error"; export type ModalMode = "alert" | "confirm";