From c069ed913572f7676304504ad53dc949f462f189 Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Sun, 26 Jul 2026 02:21:16 +0300 Subject: [PATCH] Updated readme --- ARCHITECTURE.md | 370 ++++++++++++++++++++++++++++++++---------------- README.md | 2 +- 2 files changed, 251 insertions(+), 121 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c3f388d..e3988c2 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,148 +1,278 @@ -# furumi architecture +# Furumi architecture -`furumi` is a single Rust binary organized around an Elm-style state/update -loop. The UI state remains synchronous and deterministic; filesystem, SQLite, -audio, networking, artwork, and media-control work is performed by runtime -services and reported back as application events. +Furumi is an autonomous music player that can cooperate with other Furumi +players. The architecture starts from one constraint: **a node must remain a +complete and useful player when every other node is unavailable**. -## Runtime flow +Networking therefore extends a local player instead of becoming a prerequisite +for it. There is no control plane, account service, canonical catalog, or +server-owned source of truth. + +## Architectural goals + +The design optimizes for five properties: + +1. **Local autonomy** — importing, browsing, playback, playlists, likes, and + history work entirely on one device. +2. **No central failure domain** — discovery, catalog exchange, streaming, and + synchronization do not depend on a Furumi-operated service. +3. **Offline tolerance** — trusted devices may change state independently and + reconcile after reconnecting. +4. **Incremental federation** — one node is complete; every additional node + increases availability and the amount of discoverable music. +5. **Explicit trust boundaries** — personal-device replication and wider + music federation are different protocols with different authority. + +These goals are more important than maintaining a globally identical view of +the network. Furumi prefers useful local progress and eventual reconciliation +over distributed consensus. + +## The node model + +Every running Furumi instance contains the same four capabilities: ```text -terminal/media/player/network events - | - v - app::event::AppEvent - | - v - input/keymap -> Action - | - v - app::update() - mutates AppState and - requests an Effect - | - v - app runtime performs I/O - | - +----> new AppEvent +┌─────────────────────────────────────────────────────────┐ +│ Furumi node │ +│ │ +│ Local library ── Playback engine ── TUI / media keys │ +│ │ │ │ +│ ├── Trusted-device replication │ +│ │ │ +│ └── Federation: discovery, catalog, audio │ +└─────────────────────────────────────────────────────────┘ ``` -`AppState` is the single UI source of truth. Rendering modules receive shared -state and do not own background tasks. Blocking database and file operations -run outside the terminal event loop. +The local SQLite library is authoritative for that node. Network data is +merged into local views or materialized as pending remote content; it does not +replace the local database with a remote database abstraction. -## Module layout +This keeps the core behavior predictable: + +- disconnecting never makes the local collection unavailable; +- downloaded content can become ordinary local content; +- a peer disappearing reduces availability but does not invalidate local + state; +- nodes may join and leave without electing a leader. + +## Two network layers + +Furumi deliberately separates **trusted-device synchronization** from +**federated music exchange**. + +### Trusted-device synchronization + +This layer connects devices owned or trusted by the same user. It carries +personal state such as: + +- likes and playlist operations; +- device membership and revocation; +- acknowledgements and synchronization progress; +- playback state, commands, and handoff information. + +Pairing establishes the trust relationship. After that, changes are replicated +through an append-only operation log and applied to materialized local tables. + +### Music federation + +Federation connects independent libraries. It provides: + +- decentralized discovery through the DHT; +- automatic publication of searchable local catalog metadata; +- richer artist and release catalogs fetched from peers; +- direct audio transfer when a selected track is not available locally. + +Federation does not grant another peer authority over personal playlists, +likes, or device membership. A node may participate in federation without +joining another user's trusted-device group. + +Keeping these layers separate prevents discovery convenience from silently +becoming a synchronization trust decision. + +## Discovery and direct communication + +Furumi separates finding content from transferring it. ```text -src/ - main.rs process, terminal, Tokio, and OS-media setup - app/ - state.rs UI and navigation state - action.rs semantic user actions - event.rs runtime-to-UI events - update.rs pure state transitions and requested effects - update_tests.rs update/selection/queue behavior tests - mod.rs runtime orchestration and effect execution - popup.rs popup submission behavior - input.rs editable text input - command.rs command model - cmdline.rs command-line execution - library/ - mod.rs SQLite-backed library operations - import.rs tags, audio metadata, and directory import - models.rs library-facing data types - tests.rs library integration tests - player/ - mod.rs rodio playback controller - analyzer.rs visualization audio analysis - federation/ - mod.rs DHT manager, search, downloads, and caching - catalog.rs peer catalog protocol and merge logic - audio.rs peer audio transport - tests.rs federation ranking/appearance tests - devices/ - tests.rs trusted-device sync tests - devices.rs trusted-device operation log and wire protocol - ui/ ratatui rendering by screen - config/ settings, logging, and keymaps - media.rs platform media-key/now-playing integration - visualizer.rs Rhai visualization host - visualizations/ bundled Rhai scripts - art.rs image decode and terminal-cell preparation - share.rs share-link parsing and generation - streaming.rs growing-file reader used during downloads + discovery plane + Local catalog ───────> DHT <─────── Other catalogs + │ + │ peer + content identity + v + direct P2P connection + ├── catalog protocol + ├── audio protocol + └── device-sync protocol ``` -Large orchestration modules are intentionally separated from their tests. -When they are split further, boundaries should follow services rather than -line count: playback coordination, network-library maintenance, device -storage, and device transport are the natural seams. +The DHT is the distributed index. Nodes publish compact searchable +descriptions of their local library and query the network without contacting a +central search service. -## Local library +Once a peer is known, communication moves to direct P2P streams provided by +iroh through `music-dht`. Furumi defines separate application protocols for +catalog requests, audio transfer, and trusted-device synchronization. This +keeps discovery traffic small and lets large or private exchanges happen only +between the participating peers. -`library::Library` owns a mutex-protected SQLite connection. It is the only -layer that issues library SQL and returns typed models to the rest of the -application. The schema covers artists, releases, tracks, artist relations, -playlists, likes, playback history, federated pending tracks, and cached -network artists. +Relay-assisted connectivity may help peers establish a route, but relays do +not become catalog authorities or application-state owners. -Imports read tags with `lofty`, inspect audio properties, calculate content -identifiers, and upsert normalized library records. File paths remain -device-local. +## Identity and content resolution -## Playback +Network operations refer to content independently of any one library row. +Content identifiers allow a node to ask: -`player::Controller` owns the rodio audio thread. The application maintains -the logical queue and playback state, while the controller receives play, -pause, seek, volume, and prefetch commands. The next source is opened early -for gapless transitions. The analyzer publishes levels and scope samples for -Rhai visualization scripts. +1. Is this track already present in my local library? +2. Does one of my trusted devices have it? +3. Which federation peers currently advertise it? -OS media commands enter through `media.rs`; current metadata and position are -published back to the platform now-playing surface. +Resolution follows that order conceptually: prefer a ready local source, reuse +known content where possible, and fetch from a peer only when necessary. -## Federation +A federated track can initially exist as a lightweight pending item in a queue +or playlist. When playback reaches it, Furumi resolves an available peer, +starts the transfer, and either uses the cache or imports the result into the +local library. The rest of the player continues to work with the same +`TrackItem` model, so local and remote availability do not require separate +playback systems. -Federation uses `music-dht` for discovery and byte streams: +## Offline-first synchronization -- the local library publishes metadata-only item specifications; -- search merges DHT records, peer catalogs, and cached metadata; -- catalog requests provide richer artist/release views; -- audio requests stream content from peers; -- downloads may remain cached or be imported into the local library. +Trusted devices do not share a live database connection. Each device records +operations locally and exchanges them when connectivity returns. -Federation is disabled until configured by the user. Paths are never -published as portable identifiers; content hashes and peer item IDs are used -instead. +The synchronization model combines: -## Trusted-device sync +- immutable operation identifiers for deduplication; +- hybrid logical timestamps for deterministic last-writer decisions; +- materialized tables for fast UI queries; +- tombstones so deletion survives offline replicas; +- per-peer acknowledgements to determine when old tombstones can be compacted; +- snapshots to repair a peer that missed older operations. -`devices.rs` implements a separate trusted-device protocol over a dedicated -ALPN. Likes, playlists, membership changes, and playback control are -represented as an append-only operation log with materialized SQLite tables. -Hybrid logical timestamps and acknowledgements make offline merging and -tombstone compaction deterministic. +This is eventual consistency scoped to a trusted device group. A temporarily +offline laptop can modify playlists, another device can continue playback, and +both can later converge without a permanently available coordinator. -Pairing uses short-lived invites. Device-local file paths are deliberately -excluded from synchronized playback tracks; receiving devices resolve them -through content IDs, their own library, or federation metadata. +Membership changes use the same replicated model. Revocation is state that +must propagate and converge, not an ephemeral server-side session flag. -## Configuration and persistence +## Playback across devices -The `directories` crate selects platform-standard config, data, and cache -locations. Settings, keymaps, device identity, and federation configuration -are separate files. SQLite databases and downloaded covers/audio are stored -under application data/cache directories rather than the repository. +Playback has one logical state but remains physically local to the device +producing audio. -## Reliability rules +The synchronized state describes the queue, current item, position, pause +state, volume, shuffle/repeat mode, and the active playback owner. Commands are +targeted and deduplicated. Handoff transfers intent and position; the receiving +device resolves the track against its own library or federation sources before +starting its audio engine. -- Terminal raw mode, bracketed paste, and keyboard enhancements are restored - on normal exit and panic. -- stderr from native audio libraries is captured into tracing so it cannot - corrupt the alternate screen. -- Blocking work is kept out of the UI loop. -- Runtime failures are converted to visible status/events where recovery is - possible. -- Device paths are not treated as portable network identities. -- Formatting, all-target compilation, Clippy, and unit tests should pass - before a release tag is pushed. +Active-device leases and idle timing prevent stale snapshots from immediately +taking control after a device reconnects. This provides practical coordination +without introducing a central playback arbiter. + +## Local application architecture + +Inside one node, Furumi uses an event-driven state machine: + +```text +terminal / player / database / network / OS media events + │ + v + AppEvent + │ + input → Action + │ + v + update(AppState) + │ + optional Effect + │ + v + asynchronous runtime work + │ + └──────────> AppEvent +``` + +`AppState` is the single source of truth for the interface. The update layer +performs deterministic state transitions and requests effects; it does not +perform blocking I/O. SQLite access, imports, artwork decoding, DHT queries, +peer transfers, and audio preparation run through runtime services and report +their results as events. + +This structure gives the TUI three important properties: + +- rendering is a pure projection of current state; +- input behavior can be tested without starting audio or networking; +- slow peers and large imports cannot block terminal interaction. + +The audio engine is similarly isolated. The application owns the logical +queue, while `player::Controller` owns rodio playback and receives explicit +commands. Prefetching prepares the next source before the current item ends. + +## Persistence boundaries + +Furumi stores different kinds of state according to their lifetime: + +| State | Storage | Role | +| --- | --- | --- | +| Library, playlists, likes, history | SQLite | Durable local source of truth | +| Device operation log and replicas | SQLite | Offline synchronization | +| Federation catalog cache | SQLite/cache | Faster network browsing | +| Audio and artwork cache | Filesystem cache | Reusable fetched data | +| Settings, keymap, identity | Platform config/data dirs | Node configuration | +| Queue and playback snapshots | Application/device sync state | Continuity and handoff | + +Caches are replaceable. The local library and device operation log are durable. +This distinction lets maintenance and recovery code discard derived network +data without risking the user's collection. + +## Failure model + +Expected failures are treated as ordinary state: + +- a DHT query may return partial results; +- an advertised peer may be offline by the time a track is requested; +- a transfer may stop and be retried through another source; +- trusted devices may reconnect with overlapping changes; +- cached metadata may be stale; +- an audio device may disappear during playback. + +The architecture avoids converting these cases into global failure. Search can +show partial data, synchronization can resume, and playback resolution can try +another source. Errors return to the application as events so the UI can expose +them without terminating the node. + +## Main implementation boundaries + +The source tree follows the architectural responsibilities: + +- `library/` owns the local catalog and import pipeline; +- `player/` owns audio playback and analysis; +- `federation/` owns DHT-facing search, peer catalogs, and audio exchange; +- `devices.rs` owns trusted-device replication and playback coordination; +- `app/` owns state transitions and runtime orchestration; +- `ui/` renders the TUI; +- `media.rs` integrates platform media controls; +- `visualizer.rs` hosts programmable Rhai visualizations. + +Dependencies should continue to point inward toward typed models and explicit +events. UI code should not own network tasks, network protocols should not +mutate UI state directly, and the local library should not depend on the +presence of federation. + +## Architectural invariants + +Future changes should preserve these rules: + +1. A node must start and play its local library without network access. +2. No central Furumi service may become required for discovery, playback, or + trusted-device synchronization. +3. Federation membership must not imply personal-device trust. +4. Remote state must be merged or cached locally, never treated as an always + available database. +5. Network and storage work must remain outside the interactive UI path. +6. More peers should improve availability; losing peers should only reduce + remote capabilities. diff --git a/README.md b/README.md index 527b117..5d375c7 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ library management are included. ## Install Download a prebuilt archive from the project releases, or build Furumi from -source with Rust 1.88 or newer: +source with Rust 1.97 or newer: ```bash cargo build --release --locked