Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

[项目主页](https://github.com/weidonglang/DevEnv-Manager) · [Release 下载](https://github.com/weidonglang/DevEnv-Manager/releases) · [完整操作手册](docs/user-guide.md) · [环境可靠性设计](docs/env-reliability.md) · [安全说明](docs/safety-and-disclaimer.md) · [问题反馈](https://github.com/weidonglang/DevEnv-Manager/issues)

面向 Windows 的开发环境诊断器与安全操作面板。当前版本:**1.5.3 Stable**。
面向 Windows 的开发环境诊断器与安全操作面板。当前版本:**1.6 Stable**。

1.5.3 是基于真实用户反馈的稳定收口版本,重点修复端口误判、外部运行时安全、MySQL 诊断可信度、Python/chsrc 可恢复路径、首次安全声明、白屏兜底、扫描体验和报告脱敏
1.6 是基于 v1.5.x 真实复测后的正式稳定版,重点补齐高风险后端确认闭环、页面说明去重与细化、桌面/下载目录分页明细、端口与服务安全保护、MySQL 修复执行保护和正式更新链路

适合:

Expand Down Expand Up @@ -37,6 +37,19 @@ DevEnv Manager 解决的是 Windows 上多个开发生态互相影响的问题
- 不适合希望软件自动接管整台机器、清理任意个人文件或替代专业包管理器的场景。
- 熟练使用 mise/asdf/Scoop/Chocolatey 且环境已经稳定的用户,可以只使用诊断能力。

## 1.6 Stable

1.6 是 v1.5.x QA 收口后的正式稳定版,重点不是扩展“系统管家”能力,而是把已经验证过的开发环境诊断、安全确认、页面说明和发布链路打通到可公开下载状态。

- 高风险操作保护:环境修复/恢复、PATH 清理、项目配置、项目端口、服务管理、Docker/WSL 系统动作、空间搬家/回滚/扩容、缓存清理、进程结束和 MySQL 修复执行均绑定后端 confirmation token。
- 页面说明:每个页面只保留一个详细“页面使用指南”,合并原功能说明卡里的能做/不会做、确认级别、备份要求和失败处理建议,避免上下重复。
- 桌面/下载急救:只读分析结果拆成摘要、分类占用分页和 Top 文件分页,文件卡显示文件名、完整路径、所在目录、大小、修改时间、类型、来源和定位状态。
- 提示体验:右下角进行中提示会保持到操作返回结果;成功/完成提示在结果出现后约 5 秒自动消失,错误提示保持可关闭。
- 系统级入口:Docker、WSL、本地服务和自卸载入口默认折叠在高级区,系统关键进程不提供结束入口。
- 发布链路:更新清单、Release asset、SHA256 和 README 同步到 1.6 Stable。

兼容说明:Tauri identifier 继续保留 `com.weidonglang.dailytools`,Rust/npm 包名继续保留 `dailytools-tauri`,用于兼容旧安装、升级路径和本地配置目录;产品展示名和 Release asset 统一使用 DevEnv Manager。

## 1.5.3 Stable

1.5.3 是质量补丁与稳定版收口,重点不是扩展系统管家能力,而是补齐已反馈问题的安全边界、误判抑制、恢复入口和发布链路。
Expand Down
4 changes: 2 additions & 2 deletions tauri/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tauri/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "dailytools-tauri",
"private": true,
"version": "1.5.3",
"version": "1.6.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 1420",
Expand Down
2 changes: 1 addition & 1 deletion tauri/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tauri/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dailytools-tauri"
version = "1.5.3"
version = "1.6.0"
description = "A lightweight Tauri rewrite of DevEnv Manager."
authors = ["weidonglang"]
edition = "2021"
Expand Down
1 change: 1 addition & 0 deletions tauri/src-tauri/src/cleanup/app_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub(crate) fn usage_item(
size,
category: category.to_string(),
suggestion: "只展示占用;请通过应用内置设置备份、迁移或清理".to_string(),
details: Vec::new(),
});
}
let size = categories.iter().map(|item| item.size).sum();
Expand Down
4 changes: 2 additions & 2 deletions tauri/src-tauri/src/cleanup/architecture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ pub fn architecture() -> CleanupArchitecture {
},
],
safety_rules: vec![
"Phase 2 必须经过扫描、选择、计划预览、二次确认、重新校验、清理和报告",
"清理必须经过扫描、选择、计划预览、二次确认、重新校验、执行和报告",
"普通文件优先移入 Windows 回收站;开发缓存只调用工具官方命令",
"系统目录、用户文档、当前项目和受管运行时始终受保护",
"默认扫描不进入桌面、下载、文档、图片、视频或音乐目录",
"回收站仅统计容量,本程序不会清空回收站",
"浏览器 Cookie、登录数据和密码存储不会进入扫描结果",
"微信、QQ 数据库和符号链接会被跳过",
"权限不足或扫描上限触发时只记录警告,不尝试提权",
"Phase 3 的桌面、下载、重复文件、应用、软件和游戏能力只展示占用与建议",
"桌面、下载、重复文件、应用、软件和游戏分析默认只展示占用与建议",
],
}
}
154 changes: 137 additions & 17 deletions tauri/src-tauri/src/cleanup/downloads.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::model::{FolderUsageItem, FolderUsageReport};
use super::model::{FolderUsageItem, FolderUsageReport, LargeFileItem};
use super::protect::is_sensitive_account_data;
use super::utils::system_time_string;
use std::collections::HashMap;
Expand All @@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};

const MAX_FOLDER_ENTRIES: usize = 100_000;
type FileRecord = (PathBuf, u64, Option<SystemTime>);

pub(crate) fn classify_file_type(path: &Path) -> &'static str {
let extension = path
Expand All @@ -26,7 +27,7 @@ pub(crate) fn classify_file_type(path: &Path) -> &'static str {
}
}

fn collect_files(root: &Path) -> (Vec<(PathBuf, u64, Option<SystemTime>)>, bool) {
fn collect_files(root: &Path) -> (Vec<FileRecord>, bool) {
let mut files = Vec::new();
let mut stack = vec![root.to_path_buf()];
let mut visited = 0_usize;
Expand Down Expand Up @@ -55,13 +56,126 @@ fn collect_files(root: &Path) -> (Vec<(PathBuf, u64, Option<SystemTime>)>, bool)
(files, truncated)
}

fn category_item(root: &Path, name: &str, size: u64, suggestion: &str) -> FolderUsageItem {
fn source_label(desktop: bool, category: &str) -> String {
if desktop {
if category == "截图" {
"桌面 / 截图".to_string()
} else {
format!("桌面 / {category}")
}
} else {
format!("下载 / {category}")
}
}

fn file_item(path: &Path, size: u64, modified: Option<SystemTime>, source_category: &str) -> LargeFileItem {
let exists = path.exists();
let directory = path
.parent()
.map(|value| value.to_string_lossy().to_string())
.unwrap_or_default();
let can_locate = !directory.is_empty() && Path::new(&directory).exists();
let file_type = classify_file_type(path).to_string();
LargeFileItem {
file_name: path
.file_name()
.and_then(|value| value.to_str())
.unwrap_or("")
.to_string(),
path: path.to_string_lossy().to_string(),
directory,
extension: path
.extension()
.and_then(|value| value.to_str())
.unwrap_or("")
.to_string(),
size,
modified_at: modified.and_then(system_time_string),
file_type: file_type.clone(),
source_category: source_category.to_string(),
exists,
can_open: exists,
can_locate,
open_status: if exists {
"文件存在,可在资源管理器中定位".to_string()
} else if can_locate {
"文件已移动或删除,请重新扫描".to_string()
} else {
"所在目录不可访问,请检查权限、云盘同步或重新扫描".to_string()
},
suggestion: if file_type == "安装包" || file_type == "压缩包" || file_type == "ISO/磁盘镜像" {
"确认不再需要后可加入归档计划;本页面不会自动删除或移动".to_string()
} else {
"先定位文件并确认用途;本页面只提供只读分析".to_string()
},
risk: if size >= 1024 * 1024 * 1024 {
"medium".to_string()
} else {
"low".to_string()
},
}
}

fn is_screenshot(path: &Path) -> bool {
let name = path
.file_name()
.and_then(|value| value.to_str())
.unwrap_or("")
.to_ascii_lowercase();
name.contains("screenshot")
|| name.contains("screen shot")
|| name.contains("截图")
|| name.contains("截屏")
}

fn file_matches_category(
name: &str,
path: &Path,
size: u64,
modified: Option<SystemTime>,
desktop: bool,
now: SystemTime,
same_size: &HashMap<u64, usize>,
) -> bool {
match name {
"超过 1GB" => size >= 1024 * 1024 * 1024,
"超过 30 天未修改" => modified.is_some_and(|time| {
now.duration_since(time).unwrap_or(Duration::ZERO)
>= Duration::from_secs(30 * 24 * 60 * 60)
}),
"截图" => desktop && is_screenshot(path),
"重复文件候选" => desktop && size > 0 && same_size.get(&size).copied().unwrap_or(0) > 1,
_ => classify_file_type(path) == name,
}
}

fn category_details(
files: &[FileRecord],
name: &str,
desktop: bool,
now: SystemTime,
same_size: &HashMap<u64, usize>,
) -> Vec<LargeFileItem> {
let mut details: Vec<_> = files
.iter()
.filter(|(path, size, modified)| {
file_matches_category(name, path, *size, *modified, desktop, now, same_size)
})
.map(|(path, size, modified)| file_item(path, *size, *modified, &source_label(desktop, name)))
.collect();
details.sort_by_key(|item| std::cmp::Reverse(item.size));
details.truncate(10);
details
}

fn category_item(root: &Path, name: &str, size: u64, suggestion: &str, details: Vec<LargeFileItem>) -> FolderUsageItem {
FolderUsageItem {
name: name.to_string(),
path: root.to_string_lossy().to_string(),
size,
category: name.to_string(),
suggestion: suggestion.to_string(),
details,
}
}

Expand All @@ -83,25 +197,15 @@ pub(crate) fn inspect_folder(root: &Path, desktop: bool) -> FolderUsageReport {
if *size >= 1024 * 1024 * 1024 {
*sizes.entry("超过 1GB").or_default() += *size;
}
let name = path
.file_name()
.and_then(|value| value.to_str())
.unwrap_or("")
.to_ascii_lowercase();
if desktop
&& (name.contains("screenshot")
|| name.contains("screen shot")
|| name.contains("截图")
|| name.contains("截屏"))
{
if is_screenshot(path) && desktop {
*sizes.entry("截图").or_default() += *size;
}
}
if desktop {
let reclaimable = same_size
.into_iter()
.filter(|(size, count)| *size > 0 && *count > 1)
.map(|(size, count)| size.saturating_mul((count - 1) as u64))
.iter()
.filter(|(size, count)| **size > 0 && **count > 1)
.map(|(size, count)| (*size).saturating_mul((*count - 1) as u64))
.sum();
sizes.insert("重复文件候选", reclaimable);
}
Expand Down Expand Up @@ -132,6 +236,20 @@ pub(crate) fn inspect_folder(root: &Path, desktop: bool) -> FolderUsageReport {
"其他",
]
};
let mut top_files: Vec<_> = files
.iter()
.map(|(path, size, modified)| {
file_item(
path,
*size,
*modified,
if desktop { "桌面 / Top 文件" } else { "下载 / Top 文件" },
)
})
.collect();
top_files.sort_by_key(|item| std::cmp::Reverse(item.size));
top_files.truncate(20);

let categories = order
.into_iter()
.filter_map(|name| {
Expand All @@ -148,6 +266,7 @@ pub(crate) fn inspect_folder(root: &Path, desktop: bool) -> FolderUsageReport {
} else {
"按类型查看并决定是否归档;本阶段只提供建议"
},
category_details(&files, name, desktop, now, &same_size),
)
})
})
Expand All @@ -162,6 +281,7 @@ pub(crate) fn inspect_folder(root: &Path, desktop: bool) -> FolderUsageReport {
path: root.to_string_lossy().to_string(),
total_bytes,
categories,
top_files,
suggestions: vec![
"本阶段只生成整理建议,不删除或移动桌面/下载文件".to_string(),
"旧安装包在归档前应确认对应软件已安装且安装包可重新获取".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions tauri/src-tauri/src/cleanup/junction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Junction-specific entry points live in `move_plan`/`migration`.
//!
//! This module exists to keep the cleanup namespace aligned with the Phase 4
//! architecture. The actual implementation is intentionally centralized so
//! This module keeps the cleanup namespace aligned with migration architecture.
//! The actual implementation is intentionally centralized so
//! source validation, copy verification, rollback records and reports cannot
//! diverge between direct Junction creation and regular move plans.
Loading
Loading