Recently, I’ve been doing some development experiments with the Tauri framework, both to learn new technologies and to validate some ideas.
First, let’s talk about how to implement file (or application) search. Generally speaking, you wouldn’t directly scan and traverse the file system because the speed is unacceptable. A typical example is the find command on Linux - if you traverse from the root, it would take at least 10 seconds to complete.
This happens to be a blind spot in my knowledge.
However, there is a general direction for the approach - searching from an index (or record table) is definitely faster than brute-force scanning and traversing the entire disk.
Windows
There’s a very magical software on Windows called Everything, and its search speed is extremely fast. Through research, I found that it works by directly reading the MFT (Master File Table) of the NTFS file system and building a very lightweight index at startup.
If you maintain an index yourself, you would also need a file change monitoring service to update your index. NTFS provides a feature called USN Journal (Update Sequence Number Journal) that records all file system changes (creation, deletion, renaming, etc.).
Fortunately, Windows official is very proactive about Rust, so there’s the official windows crate to help us operate the Windows system, as well as some available crates like: winreg, winapi.
Unfortunately, I have no experience with Windows API at all.
macOS
macOS itself has a metadata table that records files on the system. Spotlight search gets its results from this metadata table. It has an mds service responsible for maintaining this metadata index table. So we find that on macOS, there’s a command called mdfind, which is the client for mds and also the command-line version of Spotlight.
For example, if I want to search for the keyword weixin:
mdfind -onlyin '/Applications' -onlyin "/System/Applications" -onlyin "/Users" "(kMDItemContentType==com.apple.application-bundle || kMDItemContentType!=com.apple.application-bundle) && kMDItemDisplayName==*weixin*"
The results are roughly the same as Spotlight.
In terms of implementation, we can use std::process::Command or tokio::process::Command to wrap the mdfind command:
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))?;
However, this operation is quite slow, taking about 1.5 seconds on my computer. This time is usable but not user-friendly. The slowness is due to the -onlyin /Users parameter.
Looking at the implementation of rubick, I found that it doesn’t have file-related search. rubick doesn’t implement app search on macOS through mdfind, but rather through system_profiler:
const profileInstalledApps = spawn('/usr/sbin/system_profiler', [
'-xml',
'-detailLevel',
'mini',
'SPApplicationsDataType',
]);
system_profiler -xml -detailLevel mini SPApplicationsDataType
It roughly lists the software at startup using the above command, and subsequent queries are based on this list. This also means it won’t find newly installed software.
The pipeline and data transfer overhead of executing mdfind using Command above is quite large, because using mdfind alone has a very impressive response speed. So the proper approach is to implement it using Apple’s native API. In native APIs, NSMetadataQuery can probably meet the requirements, and for Rust, it’s in the objc2-foundation crate.
However, Rust versions of objc2 related crates lack documentation, and the implementation approach of the Cocoa framework differs significantly from Rust’s code paradigms, making it very difficult to use.
Linux
There are too many Linux distributions, so here we’ll only discuss GNOME desktop and KDE desktop. If you want to use Command wrapping, you can look into GNOME’s gio and KDE’s krunner.