使用Rust+Tauri实现了一个跨平台磁盘空间可视化工具

1,058 阅读3分钟

1.需求

我相信不少人知道一款Windows上的磁盘空间可视化神器SpaceSniffer image.png

在磁盘空间不足的时候,这个工具能轻松地帮你找出磁盘中的大文件夹,来清理用不到的文件。不过这个工具也存在一个问题,就是仅支持windows平台。正好我自己的mac空间满了,于是就想着开发一个跨平台的磁盘空间可视化工具。

2.技术选择

我选择了以下技术进行开发

  • Rust 代码性能高
  • Tauri Electron打包太大,Tauri打出来的包体积可能只有几分之一。
  • Vue 前端界面
  • ECharts 磁盘空间可视化信息展示

3.开发

ECharts的磁盘可视化需要的json信息为: image.png

于是我创建了一个struct用于传递给前端

#[derive(Debug, Serialize)]
pub struct FileNodeForJs {
    pub name: String,
    pub path: String,
    pub value: u64,
    pub children: Vec<FileNodeForJs>,
}

磁盘空间可视化最简单的办法是递归,这里用一段伪代码来简单描述获取文件大小的原理

function calculate_folder_size(folder):
    total_size = 0 
    for item in folder:
        if item is a file:
          total_size += size_of(item) 
        else if item is a folder: 
          total_size += calculate_folder_size(item)
    return total_size

但是实际上用这种方法开发会有一个明显的问题,就是只有当整个文件夹下所有内容都被扫描完成了,才会返回结果。那么我们如何实现SpaceSniffer一样的实时展示扫描结果呢?当然是传入一个静态变量啦!

#[derive(Debug, Clone)]
pub struct FileNode {
    pub name: String,
    pub path: String,
    pub value: u64,
    pub children: Vec<Arc<RwLock<FileNode>>>,
}

然后使用lazy_static!宏命令创建一个静态变量

lazy_static! {
    static ref ROOT_NODE: Arc<RwLock<FileNode>> = Arc::new(RwLock::new(FileNode::empty()));
}

然后我们就可以扫描文件夹内容的时候每次修改静态变量,查询的时候也查询这个静态变量即可。

核心代码如下:

fn explore_directory(dir: &str, current_node: Arc<RwLock<FileNode>>, depth: usize) -> u64 {
    if SHOULD_STOP.load(Ordering::SeqCst) {
        return 0;
    }
    let mut total_size = 0u64;
    if let Ok(entries) = std::fs::read_dir(dir) {
        for entry in entries {
            if let Ok(entry) = entry {
                let path = entry.path();
                if path.is_symlink() {
                    continue;
                }
                if path.is_dir() {
                    if depth <= 5 {
                        let child_node = Arc::new(RwLock::new(FileNode {
                            name: path.file_name().unwrap().to_str().unwrap().to_string(),
                            path: path.to_str().unwrap().to_string(),
                            value: 0,
                            children: vec![],
                        }));
                        current_node.write().unwrap().children.push(child_node);
                        let child_size = explore_directory(path.to_str().unwrap(), Arc::clone(&current_node.read().unwrap().children.last().unwrap()), depth + 1);
                        current_node.write().unwrap().value += child_size;
                        total_size += child_size;
                    } else {
                        let child_size = explore_directory(path.to_str().unwrap(), Arc::new(RwLock::new(FileNode::empty())), depth + 1);
                        current_node.write().unwrap().value += child_size;
                        total_size += child_size;
                    }
                } else if let Ok(metadata) = std::fs::metadata(&path) {
                    let file_size = metadata.len();
                    current_node.write().unwrap().value += file_size;
                    total_size += file_size;
                    if file_size > 5 * 1024 * 1024 && depth <= 5 {
                        let child_node = FileNode {
                            name: path.file_name().unwrap().to_str().unwrap().to_string(),
                            path: path.to_str().unwrap().to_string(),
                            value: file_size,
                            children: vec![],
                        };
                        current_node.write().unwrap().children.push(Arc::new(RwLock::new(child_node)));
                    }
                }
            }
        }
    }
    total_size
}

此处进行了一部分优化,分别是递归深度>5和文件<5Mb时,仅统计大小,不将文件加入FileNode中,原因是如果什么都加入FileNode,整个对象就会非常大,尤其是全盘扫描的时候。

剩下的一些小细节的部分我就忽略啦!

4.效果

image.png

image.png

image.png

此工具完全开源,希望各位能够提供宝贵的意见,谢谢啦。

GITHUB地址

GITEE地址