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
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.
+1 -1
View File
@@ -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