System Overview¶
neegde is a Tauri 2 desktop app. The process boundary is between the Rust backend (the native process) and the WebView frontend (Vue 3 TypeScript). Communication happens via Tauri IPC: the frontend calls invoke("command_name", args) and receives a serialized response; the backend emits events via app.emit("event_name", payload).
Process layout¶
┌─────────────────────────────────────────────────────────────┐
│ WebView (Chromium / WKWebView) │
│ │
│ Vue 3 TypeScript │
│ ├── SearchEngine (engine.ts) provider registry │
│ ├── SearchSession (session.ts) per-query state │
│ ├── Providers (rutracker.ts, soulseek.ts) │
│ ├── Pipeline (normalize/dedup/score/filter) │
│ ├── Stores (queue, entities, search, likes) │
│ └── Components (Player, Results, TorrentView, ...) │
│ │
│ Tauri IPC: invoke() / listen() │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ Rust backend (tokio async runtime) │
│ │
│ ├── resolver/ query intent resolver (Brave) │
│ ├── rutracker/ HTTP session, search, HTML parser │
│ ├── soulseek/ binary protocol, peer transfer │
│ ├── vozduxan_stream safe wrapper, token buckets │
│ ├── vozduxan_ffi raw unsafe C bindings │
│ ├── torrent_stream/ librqbit export, AppDebugLog │
│ ├── torrent_image cover art via librqbit │
│ └── app_paths canonical data directory paths │
│ │
│ FFI boundary │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ C++ vozduxan (libvozduxan.a, libtorrent) │
│ │
│ ├── Session (libtorrent session) │
│ ├── HTTP server (localhost, Range requests) │
│ ├── Piece priority scheduler (3-tier deadlines) │
│ └── Seek cancellation (seek_generation counter) │
└─────────────────────────────────────────────────────────────┘
Tauri managed state¶
All shared Rust state is held as Tauri managed state, registered in lib.rs:
app.manage(VozduxanStreamState::new(&app, debug_log.clone()));
app.manage(ResolverState::new(debug_log.clone()));
app.manage(TorrentStreamState::new(&app, debug_log.clone()));
app.manage(TorrentImageState::new(&app, debug_log.clone()));
app.manage(RutrackerState::new(&app));
app.manage(SoulseekState::new());
Tauri command handlers receive state via tauri::State<'_, T> parameters — the framework resolves them from the managed state map.
Critical invariant: VozduxanStreamState, TorrentImageState, and TorrentStreamState must share the same Arc<AppDebugLog>. They are wired together in lib.rs:
let debug_log = Arc::new(AppDebugLog::new(...));
let vzd = VozduxanStreamState::new(&app, debug_log.clone());
let img = TorrentImageState::new(&app, debug_log.clone());
let ts = TorrentStreamState::new(&app, debug_log.clone());
A separate Arc for any of them would mean their logs go to a different ring buffer and do not appear in the in-app debug console.
Audio playback path¶
User clicks track
│
▼ invoke("torrent_prepare_stream", { magnet, fileIdx, torrentFileB64 })
│
├── [RuTracker path]
│ vozduxan C++ ──→ libtorrent: open torrent, priority scheduling
│ ──→ localhost HTTP server: prebuffer 32–512 KB
│ ──→ return { url: "http://127.0.0.1:PORT/stream/TOKEN" }
│
└── [SoulSeek path]
invoke("soulseek_prepare_stream", { username, filepath, filesize })
──→ TCP to peer: download to temp file
──→ localhost HTTP server
──→ return { url: "http://127.0.0.1:PORT/..." }
│
▼
<audio src={url} /> begins playback
│
├── timeupdate event
│ invoke("vozduxan_notify_position", { token, byteOffset })
│ ──→ C++: slide piece priority window
│
└── track change / component unmount
invoke("torrent_release_stream", { token })
──→ C++: join priority thread (~100 ms), mark stream as idle
Search path¶
User types query
│
▼ store.runSearch(query)
│
▼ engine.query(rawQuery, enabled)
│
├── resolveQuery(rawQuery) [Tauri IPC → Rust]
│ Brave Search → parse Genius page titles
│ return { canonical: { artist, title }, intent, candidates }
│
├── build providerQ = "Artist Title" (brackets stripped)
│
└── new SearchSession(providerQ, { providers, pipeline })
│
├── [RuTracker provider, parallel]
│ rutracker_search(query) → topic rows
│ for each topic: getTorrentDetails → Album + Track entities
│ yield snapshots as topics complete
│
└── [SoulSeek provider, parallel]
soulseekSearch(query, requestId) → start backend search
listen("soulseek-search-batch", ...) → peer responses arrive
yield groupSlskRowsToEntities(raw) on each batch
│
▼ on each rAF
flatten all snapshots → pipeline (dedup → score → filter)
→ session.results.value updated → Vue re-renders
Module responsibilities¶
| Module | Language | Responsibility |
|---|---|---|
vozduxan (C++) |
C++ | libtorrent session, HTTP server, piece priority |
vozduxan_ffi.rs |
Rust | Raw unsafe C bindings to vozduxan |
vozduxan_stream.rs |
Rust | Safe wrapper, token buckets, Tauri commands |
resolver/ |
Rust | Query canonicalization via Brave Search |
rutracker/ |
Rust | RuTracker HTTP auth, search, HTML parsing |
soulseek/ |
Rust | SoulSeek binary protocol, search, transfer |
torrent_stream/ |
Rust | librqbit-based file export, AppDebugLog |
torrent_image.rs |
Rust | Cover art via transient librqbit sessions |
app_paths.rs |
Rust | Canonical path helpers (single source of truth) |
search/engine.ts |
TypeScript | Provider registry, session cache |
search/session.ts |
TypeScript | Per-query state, rAF flushing |
search/providers/ |
TypeScript | RuTracker and SoulSeek provider implementations |
search/pipeline/ |
TypeScript | normalize / dedup / score / filter stages |
stores/ |
TypeScript | Vue reactive state (queue, entities, search, likes) |
persistence/ |
TypeScript | localStorage serialization |