Building a Cross-Platform File Search App With Tauri — Not Electron

Published: (February 26, 2026 at 12:42 AM EST)
7 min read
Source: Dev.to

Source: Dev.to

The Problem

Every knowledge worker I know has the same issue: files are scattered across Google Drive, Dropbox, SharePoint, Slack, Notion, GitHub, and the local machine.
When you need to find something you end up opening four different search bars.

The Solution – OmniFile

OmniFile is a single, global‑shortcut‑triggered search bar that finds files across all those sources instantly.

  • Desktop app
  • Privacy‑first – everything stays on your machine

I built it with Tauri + Rust instead of Electron, and integrated seven OAuth providers directly into the desktop app.


Why Tauri + Rust?

Tauri (Rust)Electron (Chromium)
Installer size~8 MB~80 MB+
Idle RAM usage~30 MB~150 MB+
Backend languageRust (fast, safe)JavaScript
CPU‑intensive tasksRust excels at indexing & file I/OSlower

The trade‑off is writing the backend in Rust instead of JavaScript, but for a file‑search app that’s a benefit – Rust’s performance for walking directories and parsing file formats is hard to beat.


Search Engine – Tantivy

Tantivy is Rust’s answer to Lucene. I use it as the local search engine that indexes everything into a single queryable index.

schema_builder.add_text_field("title",    TEXT | STORED);      // Tokenized + returned
schema_builder.add_text_field("path",     STRING | STORED);    // Exact match
schema_builder.add_text_field("content", TEXT);               // Searchable but NOT stored
schema_builder.add_text_field("source",   STRING | STORED);    // "local", "gdrive", etc.
schema_builder.add_i64_field("modified_at", INDEXED | STORED);

Key decision – store only metadata

  • Content is indexed but not stored.
  • The original file already lives on disk, so we re‑extract its content when we need to display it.
  • This keeps the index small while still enabling full‑text search.

Provider‑Specific Indexing

Each cloud provider writes into the same Tantivy index, distinguished by a source tag.

let source_term = Term::from_field_text(source_field, "gdrive");
writer.delete_term(source_term);  // Clear only gdrive docs

// …re‑index gdrive files…

writer.commit()?;

Delete‑and‑re‑index per provider means independent updates without touching other sources.


File Content Extraction

FormatExtraction steps
DOCX1. Open the ZIP archive
  1. Locate word/document.xml
  2. Parse XML and pull text from “ tags | | XLSX | 1. Open the ZIP archive
  3. Read the shared‑strings table
  4. Resolve cell indices to actual strings | | Plain text | Try UTF‑8 → fall back to Shift‑JIS (common for Japanese) → finally lossy UTF‑8 |

OAuth in a Desktop App

Desktop apps can’t receive OAuth callbacks via a public URL.
Solution: spin up a temporary local HTTP server for each OAuth flow.

Port mapping per provider

ProviderLocal port
Google Drive14200
Dropbox14201
Box14202
SharePoint14203
Slack (HTTPS)14204
Notion14205
GitHub14206

Flow:

  1. Open the system browser.
  2. User logs in.
  3. Provider redirects to http://localhost:PORT/callback?code=XXX.
  4. Local server captures the code and exchanges it for a token.

All providers use PKCE (Proof Key for Code Exchange) to protect against authorization‑code interception – especially important for desktop apps.

Slack’s HTTPS requirement

Slack only accepts https://localhost redirects.
I generate a self‑signed TLS certificate on‑the‑fly with rcgen and serve the callback over HTTPS:

let subject_alt_names = vec!["localhost".to_string(), "127.0.0.1".to_string()];
let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names)?;

let config = rustls::ServerConfig::builder()
    .with_no_client_auth()
    .with_single_cert(vec![cert_der], key_der)?;

let tls_acceptor = TlsAcceptor::from(Arc::new(config));

The browser shows a certificate warning; the user clicks through, and the flow completes.
A retry loop handles the initial TLS handshake failure:

let mut tls_stream = match tls_acceptor.accept(stream).await {
    Ok(stream) => stream,
    Err(_) => continue, // Browser cert warning – wait for retry
};

Important: Bind the TCP listener before opening the browser to avoid a race condition where the redirect arrives before the server is ready.


GitHub Integration

GitHub’s Search API is limited (not all files indexed, stale data).
Instead I use the Trees API to fetch the entire repository file tree in one recursive call:

GET /repos/{owner}/{repo}/git/trees/{branch}?recursive=1
  • Cache results for 5 minutes.
  • Perform case‑insensitive matching client‑side.
  • Batch tree fetches (10 repos at a time) with futures::future::join_all to stay within rate limits.

If a cache refresh fails, the app gracefully falls back to the stale cache – better to show slightly outdated results than nothing.


Ranking Search Results

  1. Exact filename match (highest)
  2. Partial filename match
  3. Shorter path depth (higher‑level files are considered more relevant)

Bringing It All Together

OmniFile is designed to pop up instantly when you hit a keyboard shortcut (like Spotlight or Alfred).
Tauri’s lightweight native webview combined with a Rust backend gives us:

  • Fast launch (global shortcut)
  • Low memory footprint
  • Privacy‑first architecture (all data stays on the user’s machine)

The result is a single, unified search experience across every place a knowledge worker stores files.

Global Shortcut Enhancements

  • Debouncing – Prevents rapid show/hide cycles when the shortcut key is held down. A 300 ms threshold stops multiple triggers.
  • Rollback on failure – If registering a new shortcut fails (e.g., the key is already claimed), the system automatically re‑registers the previous shortcut so the user never loses the ability to summon the app.
  • Three‑level persistence
    1. Stored in memory for fast access.
    2. Persisted to a settings file so it survives restarts.
    3. Reflected in the tray‑menu label for user visibility.

iCloud Drive Search (No Public API)

Apple doesn’t expose a public API for iCloud Drive search. The solution is simple:

  1. Detect the iCloud Drive folder at

    ~/Library/Mobile Documents/com~apple~CloudDocs
  2. Treat it as a regular local directory.

  3. Use a file watcher to pick up changes, index the contents with Tantivy, and provide search results.

This works as long as iCloud Drive syncs files to disk (the default behavior on macOS). No OAuth flow or API integration is required.


Current To‑Do List

  1. Unify OAuth config structs – I have seven separate *OAuthConfig structs that are ~90 % identical. A trait‑based approach would eliminate duplication.
  2. Use Tantivy’s query parser for scoring – The current implementation iterates all documents with substring matching. Switching to Tantivy’s built‑in BM25 scoring will be more sophisticated and faster for large indexes.
  3. Plan for token refresh from day one
    • Some providers (Slack, Notion, GitHub) issue non‑expiring tokens.
    • Others (Google, Microsoft) require refresh‑token flows.
      This divergence currently creates special cases throughout the codebase.

Tech Stack

LayerTechnology
FrameworkTauri 2 (Rust backend + native WebView)
FrontendReact 19 + TypeScript + Tailwind CSS
Search EngineTantivy (Rust, full‑text search)
OAuthoauth2 crate + PKCE
TLSrustls + rcgen (for Slack)
File Watchingnotify crate with debouncer
File Parsingquick-xml (DOCX/XLSX), encoding_rs (Shift‑JIS)

omnifile.app

If you’re tired of searching five different places for one file, try omnifile.app.

  • Free tier – Local search only.
  • Pro tier – $129 (lifetime) unlocks all cloud integrations.

Community Questions

  • Which cloud integrations matter most to you?
  • Would you use a CLI version alongside the GUI?
  • Any clever search UX ideas?

Drop a comment or find me on GitHub.


Project Highlights

  • Built as a solo project with Tauri + Rust.
  • The entire app idles at ~30 MB RAM.
  • Every search query stays on your machine — no server, no telemetry.
0 views
Back to Blog

Related posts

Read more »

ClamAv with Rust-TUI

!Cover image for ClamAv with Rust‑TUIhttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s...