mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 01:45:54 +08:00
feat: 新增 PATH 智能分析功能 — 冲突检测 + 工具清单
- scan_conflicts: 检测不同目录中的同名可执行文件(遮蔽冲突) - scan_tools: 扫描各目录提供的可执行文件,支持关键词搜索 - Rust scanner.rs 后端,前端 AnalyzeDialog 弹窗 - 工具栏新增「分析」按钮 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
pub mod registry;
|
||||
pub mod system;
|
||||
pub mod backup;
|
||||
pub mod fs;
|
||||
pub mod disabled;
|
||||
pub mod fs;
|
||||
pub mod registry;
|
||||
pub mod scanner;
|
||||
pub mod system;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const EXECUTABLE_EXTENSIONS: &[&str] = &["exe", "bat", "cmd", "com", "ps1"];
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
pub struct ConflictLocation {
|
||||
pub dir: String,
|
||||
pub priority: usize,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
pub struct ConflictEntry {
|
||||
pub name: String,
|
||||
pub locations: Vec<ConflictLocation>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct ToolGroup {
|
||||
pub dir: String,
|
||||
pub exists: bool,
|
||||
pub exes: Vec<String>,
|
||||
}
|
||||
|
||||
/// 扫描 PATH 中的可执行文件冲突
|
||||
///
|
||||
/// 遍历每个 PATH 目录,查找 .exe/.bat/.cmd/.com/.ps1 文件,
|
||||
/// 标记出现在多个目录中的同名文件(后面的目录会被前面的「遮蔽」)
|
||||
#[tauri::command]
|
||||
pub fn scan_conflicts(paths: Vec<String>) -> Result<Vec<ConflictEntry>, String> {
|
||||
// exe_name (小写) → [(priority, dir)]
|
||||
let mut map: HashMap<String, Vec<(usize, String)>> = HashMap::new();
|
||||
|
||||
for (priority, dir) in paths.iter().enumerate() {
|
||||
let p = Path::new(dir);
|
||||
if !p.is_dir() {
|
||||
continue;
|
||||
}
|
||||
let entries = fs::read_dir(p).map_err(|e| format!("无法读取目录 {}: {}", dir, e))?;
|
||||
for entry in entries.flatten() {
|
||||
let fname = entry.file_name();
|
||||
let name = fname.to_string_lossy();
|
||||
if let Some(ext) = Path::new(name.as_ref()).extension() {
|
||||
let ext_lower = ext.to_ascii_lowercase();
|
||||
if EXECUTABLE_EXTENSIONS.contains(&ext_lower.to_str().unwrap_or("")) {
|
||||
let key = name.to_lowercase();
|
||||
map.entry(key).or_default().push((priority, dir.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut results: Vec<ConflictEntry> = map
|
||||
.into_iter()
|
||||
.filter(|(_, locs)| locs.len() >= 2)
|
||||
.map(|(name, locs)| ConflictEntry {
|
||||
name,
|
||||
locations: locs
|
||||
.into_iter()
|
||||
.map(|(priority, dir)| ConflictLocation { dir, priority })
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
results.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// 扫描 PATH 中各目录提供的可执行文件
|
||||
///
|
||||
/// query 非空时只返回文件名包含关键词的结果
|
||||
#[tauri::command]
|
||||
pub fn scan_tools(paths: Vec<String>, query: String) -> Result<Vec<ToolGroup>, String> {
|
||||
let query_lower = query.to_lowercase();
|
||||
let mut groups: Vec<ToolGroup> = Vec::new();
|
||||
|
||||
for dir in &paths {
|
||||
let p = Path::new(dir);
|
||||
if !p.is_dir() {
|
||||
groups.push(ToolGroup {
|
||||
dir: dir.clone(),
|
||||
exists: false,
|
||||
exes: vec![],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let entries = fs::read_dir(p).map_err(|e| format!("无法读取目录 {}: {}", dir, e))?;
|
||||
let mut exes: Vec<String> = Vec::new();
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let fname = entry.file_name();
|
||||
let name = fname.to_string_lossy();
|
||||
if let Some(ext) = Path::new(name.as_ref()).extension() {
|
||||
let ext_lower = ext.to_ascii_lowercase();
|
||||
if EXECUTABLE_EXTENSIONS.contains(&ext_lower.to_str().unwrap_or("")) {
|
||||
if query_lower.is_empty() || name.to_lowercase().contains(&query_lower) {
|
||||
exes.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exes.sort();
|
||||
groups.push(ToolGroup {
|
||||
dir: dir.clone(),
|
||||
exists: true,
|
||||
exes,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(groups)
|
||||
}
|
||||
@@ -28,6 +28,8 @@ pub fn run() {
|
||||
commands::fs::read_text_file,
|
||||
commands::disabled::save_disabled_state,
|
||||
commands::disabled::load_disabled_state,
|
||||
commands::scanner::scan_conflicts,
|
||||
commands::scanner::scan_tools,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
Reference in New Issue
Block a user