从命令行到桌面应用:如何参考 Mole 实现 FreshDisk 桌面版

  • King
  • 发布于 1天前
  • 阅读 18

深度解析macOS清理工具从CLI到GUI的技术迁移之路为什么做这个软件上一篇文章发布后,有读者留言说:"Mole很好用,但家里人不习惯用命令行,有没有图形界面版本?"这个需求很真实。Mole的功能确实强大,但终端工具天然有门槛——需要记住命令、理解输出、忍受全英文界

深度解析 macOS 清理工具从 CLI 到 GUI 的技术迁移之路

为什么做这个软件

上一篇文章发布后,有读者留言说:

"Mole 很好用,但家里人不习惯用命令行,有没有图形界面版本?"

这个需求很真实。Mole 的功能确实强大,但终端工具天然有门槛——需要记住命令、理解输出、忍受全英文界面。

对于普通用户来说,清理 Mac 空间这样高频的需求,值得拥有一个更友好的图形界面。

同时,市面上的清理软件要么收费昂贵,要么功能单一,要么广告满天飞。

我们希望做一个开源、免费、功能完整的 macOS 清理工具,让每个人都能轻松管理自己的 Mac 存储空间。

FreshDisk 就是这个尝试的产物——保留 Mole 的核心清理能力,用现代化的桌面界面呈现。

项目背景

1.1 Mole 命令行工具

Mole 是一个用 Go + Shell 编写的 macOS 终端清理工具,提供八大功能模块:

模块 功能
analyze 磁盘空间深度分析
status 实时系统监控
clean 缓存、日志清理
purge 开发者缓存清理
optimize 系统优化维护
uninstall 应用卸载
installer 安装包管理
touchid 权限配置

核心逻辑在 lib/ 目录下,以 Shell 脚本形式组织。

1.2 FreshDisk 桌面应用

FreshDisk 基于 Tauri 2.0 + React 19 构建,采用前后端分离架构:

  • 前端:React + TypeScript + Vite
  • 后端:Rust + Tauri IPC
  • 通信:Commands + Events
React  →  Tauri IPC  →  Rust 后端  →  系统调用

技术栈对比

维度 Mole FreshDisk
UI 框架 Bubble Tea (TUI) Tauri + React
核心语言 Go + Shell Rust + TypeScript
交互模式 同步阻塞 异步事件驱动
进度反馈 日志输出 实时进度条

核心迁移策略

3.1 分层迁移架构

┌─────────────────────────────────────────┐
│            React 前端层                  │
│   Workspace / Panel / Dialog / Progress │
├─────────────────────────────────────────┤
│            Tauri IPC 层                  │
│        Commands + Events + State        │
├─────────────────────────────────────────┤
│            Rust 后端层                   │
│        analyze / clean / status 模块     │
├─────────────────────────────────────────┤
│            系统调用层                    │
│    sysinfo / glob / trash / walkdir     │
└─────────────────────────────────────────┘

3.2 模块映射

Mole 模块 FreshDisk 模块 迁移方式
lib/core/ src-tauri/src/core/ 重构为 Rust
lib/clean/*.sh src-tauri/src/clean/ 重写逻辑
cmd/analyze/ src-tauri/src/analyze/ 复用思路
lib/uninstall/ src-tauri/src/uninstall/ 待实现

关键技术实现

4.1 路径配置提取

将 Shell 中的清理路径提取为 Rust 常量:

// 常量定义
pub const BROWSER_CACHE_PATHS: &[&str] = &[
    "~/Library/Caches/com.apple.Safari",
    "~/Library/Caches/com.google.Chrome",
];

pub const DEV_CACHE_PATHS: &[&str] = &[
    "~/Library/Developer/Xcode/DerivedData",
    "node_modules",
    "__pycache__",
];

pub const PROTECTED_BUNDLES: &[&str] = &[
    "com.apple.Safari",
    "com.apple.finder",
];

4.2 文件操作安全

迁移 Mole 的 lib/core/file_ops.sh 逻辑:

// 验证路径安全性
pub fn validate_path(path: &Path) -> Result<(), Error> {
    // 防止路径遍历
    // 检查白名单保护
    // 验证系统关键路径
}

// 安全删除(到回收站)
pub fn safe_remove(path: &Path) -> Result<(), Error> {
    if is_protected(path)? {
        return Err(Error::ProtectedPath);
    }
    trash(path)?;
    Ok(())
}

4.3 并发扫描

复用 Mole 的 cmd/analyze/scanner.go 并发思路:

// 使用 walkdir + rayon 并行扫描
pub fn scan_directory(path: &Path) -> Vec<ScanItem> {
    WalkDir::new(path)
        .into_iter()
        .par_bridge()  // 并行处理
        .filter_map(|e| e.ok())
        .filter(|e| e.metadata().is_ok())
        .map(|e| ScanItem {
            path: e.path().to_string(),
            size: e.metadata().unwrap().len(),
        })
        .collect()
}

4.4 进度反馈机制

通过 Tauri Events 实现实时进度:

// Rust 后端:发送进度
window.emit("scan_progress", Progress {
    percentage: (scanned * 100 / total) as u8,
    current_path: path.to_string(),
})?;

// TypeScript 前端:监听进度
listen<Progress>("scan_progress", (event) => {
    setProgress(event.payload);
});

4.5 系统状态监控

使用 sysinfo crate 实现状态监控:

use sysinfo::{System, SystemExt, CpuExt, DiskExt};

pub fn get_status() -> SystemStatus {
    let mut sys = System::new_all();
    sys.refresh_all();

    SystemStatus {
        cpu: sys.global_cpu_info().cpu_usage(),
        memory: sys.used_memory() as f64 / sys.total_memory() as f64,
        disks: sys.disks().iter().map(|d| DiskStatus {
            mount: d.mount_point().to_string(),
            used_percent: (d.total_space() - d.available_space()) as f64 / d.total_space() as f64,
        }).collect(),
    }
}

4.6 Homebrew 集成

通过子进程调用 brew 命令:

pub fn brew_uninstall(name: &str) -> Result<(), Error> {
    let output = Command::new("brew")
        .args(&["uninstall", "--cask", "--zap", name])
        .output()?;

    if !output.status.success() {
        return Err(Error::BrewFailed);
    }

    // 自动清理依赖
    Command::new("brew").args(&["autoremove"]).output()?;
    Ok(())
}

核心流程图

5.1 扫描流程

用户点击扫描
     ↓
React:invoke('clean_scan_all')
     ↓
Rust:scan_cleanable_items()
     ↓
使用 glob 匹配路径
     ↓
WalkDir 遍历文件
     ↓
计算文件大小
     ↓
Window.emit('progress')
     ↓
React 更新进度条
     ↓
返回扫描结果

5.2 清理流程

用户确认清理
     ↓
检查白名单保护
     ↓
验证路径安全性
     ↓
使用 trash crate 删除
     ↓
记录清理结果
     ↓
发送完成通知

关键技术难点

6.1 Shell 命令替代

Shell 命令 Rust 方案
find walkdir
du walkdir + std::fs
xargs rayon
grep regex

6.2 权限处理

使用 objc2 调用 AppKit 处理 Touch ID 和完整磁盘访问:

// 请求完整磁盘访问权限
pub fn request_full_disk_access() -> bool {
    let panel = NSOpenPanel::new();
    panel.setCanChooseDirectories(true);
    panel.runModal() == NSModalResponseOK
}

6.3 状态同步

  • 同步操作:使用 invoke 等待结果
  • 异步操作:使用 Events 实时推送
  • 状态管理:React Context + Hooks

性能优化

7.1 并行扫描

// rayon 并行处理大目录
items.par_iter().for_each(|item| {
    let size = calculate_size(item.path);
    // ...
});

7.2 增量扫描

缓存上次扫描结果,只扫描变化的文件:

pub fn incremental_scan(path: &Path, last: &ScanSnapshot) -> ScanDelta {
    // 比较文件修改时间
    // 只返回新增和修改的项目
}

7.3 LRU 缓存

// 缓存目录大小计算结果
let cache = Mutex::new(LruCache::new(100));

总结

迁移要点

  1. 配置提取:将 Shell 变量转为 Rust 常量
  2. 逻辑重写:Bash 逻辑重写为 Rust
  3. 架构统一:Tauri IPC 解耦前后端
  4. 体验升级:事件驱动的进度反馈

推荐顺序

  1. analyze → 磁盘分析(核心)
  2. status → 状态监控(简单)
  3. clean → 深度清理(高频)
  4. purge → 项目清理(进阶)
  5. uninstall → 应用卸载(复杂)
  6. optimize → 系统优化(收尾)

技术选型

场景 推荐
文件遍历 walkdir
并行处理 rayon
系统信息 sysinfo
安全删除 trash
路径匹配 glob + regex

通过以上策略和技术选型,FreshDisk 成功实现了从 Mole 命令行工具到桌面应用的迁移,提供了更友好的用户体验和强大的功能。

希望这篇文章能为有类似需求的开发者提供参考和帮助。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发