Skip to content
Rymembra Docs GitHub

Docs

Overview

The rules the whole codebase follows, the crates and what each may depend on, the threads of a running process, the error model, and a glossary of the Italian identifiers.

Design principles#

Source core/rymembra-domain/src/lib.rs core/rymembra-app/src/lib.rs core/rymembra-net/src/lib.rs Cargo.toml PRIVACY.md

A handful of rules recur in every crate. Most of them are enforced by the type system or by a test, not by convention.

The domain decides, the application executes

rymembra-domain has no I/O, no clock and no global state: every function is a pure function. The decisions that can lose data live there — which rows a scan removes (plan_scan), whether two files are the same track (TrackKey, ContentKey), how two copies of a play count merge (merge_stats), what a restore would change (plan_restore). They are tested as plain calls, and several are checked against golden outputs recorded from the previous TypeScript implementation (core/rymembra-domain/tests/parita_*.rs). rymembra-app owns the database, the filesystem and the clock, and only executes those plans.

No lock is held while waiting for the network or a slow disk

Every crate that talks to the outside world — rymembra-net, rymembra-meta, rymembra-catalogo, rymembra-cloud, rymembra-scrobble, rymembra-ia, rymembra-archivio, rymembra-sync, rymembra-insieme — has no access to rusqlite. Holding the library lock during a request therefore cannot be written: the type that would be needed never crosses the crate boundary. Long jobs are split into phases that take and release the lock:

candidates()  →  decide()      →  apply()      →  record()
(lock held)      (no lock,        (no lock,        (lock held,
                  network)         disk)            one transaction)

Your music files are not written

Metadata found online, corrections, covers, lyrics, plays and ratings live in the library database (rymembra.db). The only writes into a music folder are: the file a download produces (tagged by tag_scrittura::scrivi_tag), an optional .lrc beside a track (testi.accanto, off by default), and the temporary «write tags back into the files» action that undoes what versions up to 2.3.0 wrote — see Enrichment.

The network only answers a feature you use

There is no telemetry and no Rymembra server. Every outbound request belongs to a feature: the free catalogs, MusicBrainz and the cover archives, LRCLIB, GitHub for updates (not from a Microsoft Store copy, which the Store updates), and — only once connected — Google Drive, ListenBrainz, Last.fm or a language model. «Listen together», when switched on, talks only to other PCs on the local network: discovery datagrams, and encrypted connections to PCs that have been paired by comparing a four-digit code (Listen together). PRIVACY.md lists each request and what it carries. Secrets (OAuth refresh tokens, API keys, client secrets) live in the OS keychain under the service io.github.federicobaratti.rymembra and are read by the core just before a request; they never travel through the window.

Blocking code, no async runtime of our own

HTTP is ureq (blocking), not reqwest; background work runs on named std::threads; cancellation is a Fn() -> bool closure or an AtomicBool polled between blocks; deadlines come from rymembra_app::scadenza. Tauri's own runtime is used only to dispatch IPC commands.

Failures are data, and they are visible

Every error is a record from one catalog (Errors). Background loops run each pass inside catch_unwind and record panics in a health registry (Runtime); the release profile keeps panic = "unwind" so that Drop runs and the SQLite WAL is checkpointed even after a panic.

Skins are data, not CSS

A skin is a typed JSON document; the compiler in rymembra-skin is the only author of CSS, and no author-written string reaches the stylesheet. See Skins.

See also: Workspace and crates, Threads, locks and the order they are taken, Errors: from the catalog to the window

Workspace and crates#

Source Cargo.toml core apps/desktop/src-tauri/Cargo.toml apps/desktop/rymembra-barra/Cargo.toml apps/desktop/package.json

One Cargo workspace at the repository root (resolver = "3", edition 2024, rust-version = "1.90", version 3.1.0) with 16 members, plus the React frontend in apps/desktop/src. A root-level cargo test builds and tests the desktop app too, not only the libraries.

CrateRoleInternal dependencies
rymembra-domainPure decisions: keys, scan plan, album grouping, matching, queue, listen rule, merge, restore plan, lyrics parser, error catalog—
rymembra-playAudio engine: decode (symphonia + Opus), resample, EQ, gain, spectrum, cpal output; fingerprint, loudness and onset analysisdomain
rymembra-appThe library on SQLite: migrations, scan, enrichment, playlists, lyrics, backup/sync bridges, profiledomain, play, meta, sync
rymembra-netBlocking HTTP (ureq): retries, redirect policy, seekable HTTP streamsdomain
rymembra-metaMusicBrainz, Cover Art Archive, iTunes, Deezer, LRCLIB, ListenBrainz; rate limiting and circuit breakerdomain, net
rymembra-catalogoFree catalogs (Internet Archive, Audius, Jamendo behind a feature): search, resolve, licence-checked downloaddomain, net
rymembra-archivioReads the Spotify GDPR zip into an AccountSnapshotdomain
rymembra-syncPer-device documents and the merge that makes them convergedomain
rymembra-cloudGoogle Drive backup in the app-data folderapp, domain, net, oauth, skin, sync
rymembra-oauthPKCE, loopback listener, keychain trait, token expirydomain
rymembra-scrobbleLast.fm and ListenBrainz protocolsdomain, net
rymembra-iaOpenAI-compatible chat client, SSE parser, patch-block extractordomain, net
rymembra-skinSkin format, validation, CSS compiler, .ryskin packagesdomain
rymembra-insiemeListen together on the LAN: discovery, Noise-encrypted connections, pairing code, file reads and relay between PCs, clock exchangesdomain
rymembra-desktopTauri 2 shell (binary rymembra): IPC commands, threads, Windows integrationall of the above
rymembra-barraTaskbar thumbnail buttons — the only crate with unsafe—

Dependency direction

rymembra-desktop ─► rymembra-cloud ─► rymembra-app ─► rymembra-play ─► rymembra-domain
                                        │        └──► rymembra-meta ─► rymembra-net ─► rymembra-domain
                                        └──► rymembra-sync ──────────────────────────► rymembra-domain
rymembra-catalogo / rymembra-scrobble / rymembra-ia ─► rymembra-net ─► rymembra-domain

The direction is binding: nothing below rymembra-app sees the database, and rymembra-cloud is never a dependency of another core crate. rymembra-app is kept platform-neutral (bundled SQLite, a MusicFiles trait instead of std::fs) because it is meant to be shared with an Android build; only Windows ships today, and rust-toolchain.toml targets x86_64-pc-windows-msvc.

Shared dependencies

Declared once in [workspace.dependencies], each with a written reason: ureq 3.4 with platform-verifier (TLS roots from the OS store), zip 8 with only deflate, blake3, sha2, keyring, encoding_rs, unicode-normalization, serde, flate2, getrandom. The frontend's runtime npm dependencies are React 19, @tauri-apps/api, @tauri-apps/plugin-dialog and CodeMirror 6.

See also: Design principles, Repository layout and conventions

Threads, locks and the order they are taken#

Source apps/desktop/src-tauri/src/stato.rs apps/desktop/src-tauri/src/main.rs apps/desktop/src-tauri/src/disparte.rs apps/desktop/src-tauri/src/riproduzione/mod.rs

Rymembra is one process: a WebView2 window and the Rust core. The tauri-plugin-single-instance guard brings an existing window to the front instead of starting a second process on the same database (it is skipped when RYMEMBRA_DATI points at another data folder).

The two locks

  • Library — Stato.libreria: Mutex<Result<Libreria, AppError>>: one SQLite connection plus the cover store. Not a pool: SQLite has one writer anyway and reads take microseconds. If the database failed to open, the error is kept and returned to every command, so the window can still start and explain.
  • Player — StatoLettore.lettore: the engine handle, the queue, the listen tracker. A scan may hold the library lock for a batch; pause must not wait for it.

Order rule: player first, then library, never the reverse. Next to the library mutex, never behind it, sit values that never change or are single bits: the cover-store path, the data folder, scansione_da_fermare (cancel) and scansione_in_corso (one scan at a time).

HelperBehaviour
con_libreriaWaits as long as needed; logs the waiting thread after ATTESA_DA_NOTARE = 2 s. On the UI thread it gives up after ATTESA_DELLA_FINESTRA = 4 s with db.locked (Windows marks a window «not responding» after 5 s).
con_libreria_entroBounded wait; None means «not executed». Used on exit (250 ms for the last PRAGMA optimize).
lucchetto_entrotry_lock every PASSO_ATTESA = 5 ms. A poisoned mutex is recovered, not treated as busy.
Turno::prendi / adottaAn AtomicBool guard released in Drop, even while unwinding. TurnoDiScansione also wakes the maintenance thread when a scan ends.

Where commands run

A plain #[tauri::command] runs on the UI thread; (async) commands run on Tauri's worker pool (one thread per core). Anything that may wait on a disk — scans, folder probes, profile export/import, lyrics lookup — and the heavy library reads go through disparte::in_disparte / leggi_la_libreria, i.e. spawn_blocking, so a dead network share cannot exhaust the workers.

Long-lived threads

ThreadModuleCadence and job
cpal callbackrymembra-play::uscitaReal time: pops samples, EQ, spectrum tap, gain, limiter
rymembra-uscitarymembra-play::uscitaOwns the stream; sleep/wake policy (1 s awake, 250 ms asleep)
rymembra-decodificarymembra-play::motoreDecodes into the ring; idle sleep 4 ms; runs under catch_unwind
rymembra-eventirymembra-play::motoreDelivers engine announcements (start, stop, error)
rymembra-seguitoriproduzioneOrdered slow work: state events, SMTC, tray, listen writes
rymembra-preparatoreriproduzione::filiOpens the next track for gapless; 15 ms debounce
rymembra-orologioriproduzione::filiEvery 250 ms: position event, saves, device loss, sleep timer
rymembra-spettroriproduzione::fili33 ms while the spectrum is visible, 250 ms otherwise
rymembra-dispositiviriproduzione::uscitePolls output devices every 2 s; reopens; backoff to 30 s
rymembra-sorvegliasorvegliaWatched folders: 2 s debounce, 60 s cap, 5 min network polling
rymembra-nuvolanuvolaDrive backup: first pass after 30 s, then every 15 min
rymembra-sincroniasincroniaDevice sync: after 20 s, then every 5 min
rymembra-arricchimentoarricchimentoEnrichment: after 90 s, then every 30 min
rymembra-manutenzionemanutenzioneAfter 60 s: quick check; optimize every 6 h; cover cleanup
rymembra-analisianalisiAfter 2 min, every 20 min; yields while music plays
rymembra-scrobblescrobbleDrains the queue on demand or every 300 s
rymembra-aggiornamentiaggiornamenti10 s after the window is ready, then every 30 min
rymembra-insiemeinsieme::filoListen together, when on: the session's single thread (network events, commands, state and anchors); helpers -rete, -esecutore (members' gestures on the host), -preparazione, -descrittori, -copertine, -campanello (turns the player's wake-ups into the session's mail, a burst counting as one) and -sincronia (a member's regulator, every 100 ms)

Other threads start on demand: rymembra-scadenza-* (one open with a deadline), rymembra-operaio-* and rymembra-a-rate-* (scan reads and walks), rymembra-volumi-*, flusso-anticipo (HTTP prefetch), rymembra-procura (downloads), rymembra-testi and rymembra-testo-dopo (lyrics), rymembra-scrobble-adesso, rymembra-ia, rymembra-aggiornamento (an install), rymembra-tasti (media keys, from the first press), cartelle-sfratto (while the Folders tree is in memory), rymembra-risveglio (wakes network roots at start).

The periodic-thread template

stato::avvia_filo_periodico is shared by backup, sync, enrichment and maintenance: a start delay, then recv_timeout(intervallo) as both the period and the wake-up channel. A wake-up that marks data dirty waits for its raffica (burst) to go quiet before the pass; aspetta_la_raffica tells «quiet» from «channel closed» so that closing the app never starts a pass. Each pass runs inside sorvegliato (catch_unwind); a panic is logged, counted in the registry read by the fili_salute command, and retried after 5 s, doubling to 10 min.

See also: The player in the desktop app, Startup and shutdown, The data folder and the database connection

Errors: from the catalog to the window#

Source core/rymembra-domain/src/errors/catalog.rs core/rymembra-domain/src/errors/mod.rs core/rymembra-domain/src/errors/rete.rs apps/desktop/src-tauri/src/errore.rs apps/desktop/src/ipc.ts apps/desktop/src/parti/Avvisi.tsx

Every failure in Rymembra is one of 153 codes declared in a single catalogo! macro invocation. One line per error generates ErrorCode (with typed parameters, #[non_exhaustive]), ErrorCodeKind (without them, with ALL), and the metadata: code() ("net.offline"), i18n_key() — always concat!("errors.", code), never written by hand —, domain(), severity(), retry_rule() and the legacy_code() of the previous app.

DomainCodesDomainCodes
net7download15
db10spotify3
fs11catalogo4
playback8skin12
library11transfer (reserved for phone transfer)9
metadata11sync7
settings7ia8
ipc3internal5
insieme (Listen together)22——

Severity is Info (an expected outcome: «no match», «cancelled»), Warning, Error or Fatal. RetryRule is Always, Never or HttpStatus, which retries only 408, 429 and ≥ 500. Retryability drives automatic retries in rymembra-net and the «Retry» buttons in the window.

AppError

AppError { code, message, cause }. message is for developers and never shown; the user-facing sentence is the translation of the i18n key with the code's parameters. with_cause flattens the cause to one line and truncates it at CAUSA_MASSIMA = 500 characters (an HTML error page from a catalog would otherwise flood the log). Display prints [code] message (causa: …).

errors::rete::e_di_rete classifies an io::Error as «network» from the raw Windows error number (51, 53, 64 ERROR_NETNAME_DELETED, 121, 1231, 2250 ERROR_NOT_CONNECTED, …). It is in the domain crate because both rymembra-app (opening a file) and rymembra-play (reading it mid-stream) must answer the same way.

Across IPC

errore::ErroreIpc { code, domain, severity, retryable, i18nKey, message, cause } is the serialized form; commands return Esito<T> = Result<T, Box<ErroreIpc>> (boxed so successful responses stay small). errore::errore() is the single bridge and writes code, severity, retryability and cause to the log.

In the window, eErroreIpc is the type guard, testoErrore returns tSe(i18nKey, message ?? cause ?? code), dettaglioErrore adds the service's own wording when it differs, and Avvisi.tsx maps info → note, retryable warning → warning with Retry, anything else → blocking alert. strumenti/lingue.js fails if a catalog code has no errors.<code> sentence in it.json.

See also: The log, Translations

Glossary of Italian identifiers#

Source core apps/desktop/src-tauri/src apps/desktop/src

Code, comments and file names are Italian. These are the words that recur; module names are in code.

ItalianMeaning in the code
brano, branitrack(s)
libreriathe library database and its connection
codathe play queue (also: the download queue in desiderati)
motoreaudio engine (rymembra-play); in rymembra-sync the merge driver
uscitaaudio output / device stream
lettorethe player state in the desktop app
riproduzioneplayback
anellolock-free ring buffer
segnoa marker telling which output frame starts which track
puntinaplayhead («stylus»)
dissolvenzacrossfade
spegnimentosleep timer (command) and app shutdown (module)
sonno / svegliaoutput stream asleep / wake-up signal
scansionescan of watched folders
radici, cartelle sorvegliatewatched root folders
sorvegliafilesystem watcher
volumilocal vs network location of a path
scadenzadeadline / timeout wrapper
lottobatch
passata, giroone pass of a background job; giro is also the guided tour
rafficaburst (debounce window)
turnoexclusive-operation guard
filo, filithread(s)
lucchettomutex lock
dispartethe blocking thread pool
arricchimentoonline metadata enrichment
incerti, correzionitracks to review, user corrections
copertinacover art
tintadominant hue of a cover
testo, testilyrics
ascoltoa listen (play event)
voto, preferitorating, like/heart
cronologiaplay history
ripresaresume point
improntasonic fingerprint (rymembra-play); content hash (sync, backup)
intensitàmeasured loudness (R128) and true peak
vicinanza, affinitàneighbours from ListenBrainz, similarity scoring
settimanathe weekly («Monday») collections
catalogo, cataloghifree online catalogs
esplorafree-text catalog search
desideratiwanted tracks: the download wishlist
procura, prelievothe download worker, fetching the bytes
abbinamento, gradinomatching against the library, rung of the matching ladder
nuvolaGoogle Drive backup
sincroniadevice sync
magazzinostorage backend of sync (folder or Drive)
fusione, lapidimerge, tombstones
portachiaviOS keychain
rete, flussoHTTP client, seekable HTTP stream
rimandi, cancelloredirect policy, redirect allow-list predicate
cadenza, depositoper-service rate limiter, response cache
profilothe .ryprofile export (also: an AI model profile)
manutenzione, salutedatabase maintenance, health
diariothe log file
vassoio, barratray icon, taskbar thumbnail buttons
scafalea skin's layout tree (shell)
parte, partirestylable UI component(s) of a skin
tavolozza, motivo, innescopalette, pattern, animation trigger
bozza, istantaneaStudio draft, snapshot
tela, mondoStudio canvas, its fake sample library
archivioa frontend store (archivio.ts); the Spotify zip (rymembra-archivio)
rada, virtualesparse paged list, virtualized list
esito, guasto, ripiegoresult, failure, fallback

See also: Workspace and crates

Repository layout and conventions#

Source Cargo.toml clippy.toml rust-toolchain.toml .gitattributes strumenti site

PathContents
core/The 14 core crates
apps/desktop/src-tauri/The Tauri shell (rymembra-desktop), tauri.conf.json, capabilities/, and msix/ — the Microsoft Store package's manifest template, identity (identita.json), logos and listing texts (scheda-store.md)
apps/desktop/rymembra-barra/Taskbar buttons crate
apps/desktop/src/React frontend; test/ holds its node --test suites
strumenti/Repository checks and release tools (Node, no dependencies)
site/The sources of the website — the landing page (one template, testi/ in four languages) and these docs — built into _site/ by strumenti/sito.js and published by .github/workflows/sito.yml
.github/workflows/verify.yml, release.yml, sito.yml, msix.yml

Lints

Workspace lints apply to every crate: unsafe_code = "forbid" (only rymembra-barra relaxes it to deny, with unsafe confined to one module), missing_docs = "warn", and Clippy deny on unwrap_used, expect_used, panic and indexing_slicing — a panic in a domain function would stop the music. clippy.toml allows all four in tests. CI builds with RUSTFLAGS=-D warnings, so every warning is an error there.

Release profile

opt-level = 3, lto = "thin", codegen-units = 1, strip = "symbols", and panic = "unwind" for three documented reasons: Drop must run so the SQLite WAL is checkpointed; analisi relies on join().ok() of scoped threads; a malformed file that panics symphonia must cost one track, not the process.

Conventions

  • Files open with a doc block whose # Perché … sections record the reason, the rejected alternative and, often, the bug that led to the current shape. Read them before changing behaviour.
  • Constants carry names and units in the identifier (ATTESA_AVVIO, RIPOSO_MS) and a comment with the reasoning behind the number.
  • Pure rules are extracted into DOM-free modules on the frontend (selezione.ts, rada.ts, modale.ts…) so node --test can load them.
  • Line endings are LF everywhere (.gitattributes: * text=auto eol=lf).
  • The toolchain is stable locally (rust-toolchain.toml) and pinned to 1.97.1 in CI through RUSTUP_TOOLCHAIN.

See also: Building and running, Repository checks (strumenti)