最近在使用 tauri 框架做一些开发实验,一来学学技术,二来验证一些想法。
首先说说如何实现文件(或者应用)搜索问题。一般来说,是不会直接对文件系统直接扫描遍历的,因为速度上无法接受。典型的例子就是 linux 上的 find 命令,如果你从根开始遍历,最快也要 10 来秒才能完成。
这些刚好是我的知识盲区。
不过思路方向大概是有的,那就是从索引(或者记录表)上找肯定要比粗暴地扫描遍历整个盘要快。
windows
windows 上有一个非常神奇的软件叫 Everything,它的搜索速度相当地快。通过了解发现它是通过直接读取 NTFS 文件系统的 MFT(主文件表),同时启动时建立一个非常轻量的索引来实现的。
如果自己自行维护一个索引,还需要一个监听文件变更服务来更新自己的索引,NTFS 提供了一个叫 USN Journal(Update Sequence Number Journal) 的功能,记录所有文件系统的更改(创建、删除、重命名等)。
幸运的地,windows 官方对 rust 非常的积极,因此有官方 windows crate 帮助我们操作 window 系统,同时也有一些可用的 crate,比如:winreg,winapi。
不幸的是,我完全没了解过 windows api。
macos
macos 本身有记录系统上的文件的元数据表。Spotlight 的搜索就是从元数据表里获取的。它有一个mds的 service 服务负责维护这个元数据索引表。所以我们会发现,在 macos 上,会有一个叫 mdfind 的命令,这就是 mds 的客户端,也算是Spotlight 的命令行版本。
比如我要搜索weixin 这个关键词:
mdfind -onlyin '/Applications' -onlyin "/System/Applications" -onlyin "/Users" "(kMDItemContentType==com.apple.application-bundle || kMDItemContentType!=com.apple.application-bundle) && kMDItemDisplayName==*weixin*"
结果大致和 Spotlight 是一样的。
应用上,我们可以使用std::process::Command 或者 tokio::process::Command 包装 mdfind 命令来实现:
let output = tokio::process::Command::new("mdfind")
.args([
"-onlyin",
"/Applications",
"-onlyin",
"/System/Applications",
"-onlyin",
"/Users",
format!(
"(kMDItemContentType==com.apple.application-bundle || kMDItemContentType!=com.apple.application-bundle) && kMDItemDisplayName==*{}*",
query
)
.as_str(),
])
.output()
.await
.map_err(|e| format!("Failed to execute mdfind command: {}", e))?;
然而,这个操作相当地慢,在我的电脑上耗时大概需要 1.5秒。这个耗时属于能用,但是不好用。慢的原因出在 -onlyin /Users 上。
看了一眼rubick 的实现,发现它没有文件相当的搜索。rubick 在 macos 上搜索 app 也不是通过 mdfind 来实现的,而是通过 system_profiler:
const profileInstalledApps = spawn('/usr/sbin/system_profiler', [
'-xml',
'-detailLevel',
'mini',
'SPApplicationsDataType',
]);
system_profiler -xml -detailLevel mini SPApplicationsDataType
大概是在启动时,使用上面的命令列出软件列表,后续查询都基于此列表。这也意味着,新安装的软件它应该会找不到。
上面使用Command 执行 mdfind 的管道和数据传输开销是比较大地,因为单纯使用 mdfind 响应速度非常可观,所以正经的操作是使用 苹果的原生 api 去实现。原生 API 里,NSMetadataQuery 大概就能满足要求,rust 的话,它位于objc2-foundation crate 中。
然而,rust 版本的 objc2 相关的 crates 缺乏文档,加上 cocoa 框架的实现方式和 rust 的代码范式差异有些大,非常难以使用。
linux
linux 太多发行版了,这里只讨论 GNOME桌面 和 KDE桌面,如果想使用 Command 包装,可以看看 GNOME 的 gio 和 KDE 的 krunner。