Skip to content

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