Updated readme

This commit is contained in:
Ultradesu
2026-07-26 02:21:16 +03:00
parent 3556120e10
commit c069ed9135
2 changed files with 251 additions and 121 deletions
+250 -120
View File
@@ -1,148 +1,278 @@
# furumi architecture # Furumi architecture
`furumi` is a single Rust binary organized around an Elm-style state/update Furumi is an autonomous music player that can cooperate with other Furumi
loop. The UI state remains synchronous and deterministic; filesystem, SQLite, players. The architecture starts from one constraint: **a node must remain a
audio, networking, artwork, and media-control work is performed by runtime complete and useful player when every other node is unavailable**.
services and reported back as application events.
## 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 ```text
terminal/media/player/network events ┌─────────────────────────────────────────────────────────┐
| │ Furumi node │
v │ │
app::event::AppEvent │ Local library ── Playback engine ── TUI / media keys │
| │ │ │ │
v │ ├── Trusted-device replication │
input/keymap -> Action │ │ │
| │ └── Federation: discovery, catalog, audio │
v └─────────────────────────────────────────────────────────┘
app::update()
mutates AppState and
requests an Effect
|
v
app runtime performs I/O
|
+----> new AppEvent
``` ```
`AppState` is the single UI source of truth. Rendering modules receive shared The local SQLite library is authoritative for that node. Network data is
state and do not own background tasks. Blocking database and file operations merged into local views or materialized as pending remote content; it does not
run outside the terminal event loop. 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 ```text
src/ discovery plane
main.rs process, terminal, Tokio, and OS-media setup Local catalog ───────> DHT <─────── Other catalogs
app/
state.rs UI and navigation state │ peer + content identity
action.rs semantic user actions v
event.rs runtime-to-UI events direct P2P connection
update.rs pure state transitions and requested effects ├── catalog protocol
update_tests.rs update/selection/queue behavior tests ├── audio protocol
mod.rs runtime orchestration and effect execution └── device-sync protocol
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
``` ```
Large orchestration modules are intentionally separated from their tests. The DHT is the distributed index. Nodes publish compact searchable
When they are split further, boundaries should follow services rather than descriptions of their local library and query the network without contacting a
line count: playback coordination, network-library maintenance, device central search service.
storage, and device transport are the natural seams.
## 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 Relay-assisted connectivity may help peers establish a route, but relays do
layer that issues library SQL and returns typed models to the rest of the not become catalog authorities or application-state owners.
application. The schema covers artists, releases, tracks, artist relations,
playlists, likes, playback history, federated pending tracks, and cached
network artists.
Imports read tags with `lofty`, inspect audio properties, calculate content ## Identity and content resolution
identifiers, and upsert normalized library records. File paths remain
device-local.
## 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 1. Is this track already present in my local library?
the logical queue and playback state, while the controller receives play, 2. Does one of my trusted devices have it?
pause, seek, volume, and prefetch commands. The next source is opened early 3. Which federation peers currently advertise it?
for gapless transitions. The analyzer publishes levels and scope samples for
Rhai visualization scripts.
OS media commands enter through `media.rs`; current metadata and position are Resolution follows that order conceptually: prefer a ready local source, reuse
published back to the platform now-playing surface. 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; Trusted devices do not share a live database connection. Each device records
- search merges DHT records, peer catalogs, and cached metadata; operations locally and exchanges them when connectivity returns.
- catalog requests provide richer artist/release views;
- audio requests stream content from peers;
- downloads may remain cached or be imported into the local library.
Federation is disabled until configured by the user. Paths are never The synchronization model combines:
published as portable identifiers; content hashes and peer item IDs are used
instead.
## 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 This is eventual consistency scoped to a trusted device group. A temporarily
ALPN. Likes, playlists, membership changes, and playback control are offline laptop can modify playlists, another device can continue playback, and
represented as an append-only operation log with materialized SQLite tables. both can later converge without a permanently available coordinator.
Hybrid logical timestamps and acknowledgements make offline merging and
tombstone compaction deterministic.
Pairing uses short-lived invites. Device-local file paths are deliberately Membership changes use the same replicated model. Revocation is state that
excluded from synchronized playback tracks; receiving devices resolve them must propagate and converge, not an ephemeral server-side session flag.
through content IDs, their own library, or federation metadata.
## Configuration and persistence ## Playback across devices
The `directories` crate selects platform-standard config, data, and cache Playback has one logical state but remains physically local to the device
locations. Settings, keymaps, device identity, and federation configuration producing audio.
are separate files. SQLite databases and downloaded covers/audio are stored
under application data/cache directories rather than the repository.
## 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 Active-device leases and idle timing prevent stale snapshots from immediately
on normal exit and panic. taking control after a device reconnects. This provides practical coordination
- stderr from native audio libraries is captured into tracing so it cannot without introducing a central playback arbiter.
corrupt the alternate screen.
- Blocking work is kept out of the UI loop. ## Local application architecture
- Runtime failures are converted to visible status/events where recovery is
possible. Inside one node, Furumi uses an event-driven state machine:
- Device paths are not treated as portable network identities.
- Formatting, all-target compilation, Clippy, and unit tests should pass ```text
before a release tag is pushed. 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.
+1 -1
View File
@@ -70,7 +70,7 @@ library management are included.
## Install ## Install
Download a prebuilt archive from the project releases, or build Furumi from 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 ```bash
cargo build --release --locked cargo build --release --locked