Files
lanspread/crates/lanspread-peer/README.md
T
ddidderr 87d00e7df6 refactor(peer): make startup directory-driven
Peer startup used to bootstrap itself by spawning the runtime and immediately
sending a SetGameDir command back through its own control channel. The Tauri
integration then polled shared state until a directory appeared and waited two
seconds before asking peers for games. That made startup ordering implicit and
left a race-prone sleep in the UI bridge.

Install the initial game directory directly into the peer context instead. The
runtime now attempts the initial local-library scan before starting discovery,
then launches the server, discovery, liveness, and local monitor services from
that initialized context. Later directory changes still use SetGameDir, so the
existing UI command surface stays intact.

Use PathBuf and Path references across peer filesystem boundaries so directory
state is represented as a path rather than an optional string. The Tauri layer
now validates a selected game directory before storing it, loads the bundled
catalog on first use, and starts or updates the peer runtime from one helper.
Peer event fan-out is split into named handlers so the Tauri setup closure only
wires state and starts the event loop.

Shutdown goodbye notifications are still best-effort, but they are now awaited
with a short timeout instead of being spawned and forgotten. The tradeoff is a
small bounded wait during peer runtime shutdown in exchange for clearer task
ownership.

Test Plan:
- cargo test -p lanspread-peer
- cargo clippy
- cargo clippy --benches
- cargo clippy --tests
- cargo +nightly fmt
- git diff --check

Refs: none
2026-05-02 17:09:00 +02:00

119 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# lanspread-peer
`lanspread-peer` is the networking runtime that lets Lanspread nodes find each
other on the local network, exchange library metadata, and transfer game files.
It is designed to run headless other crates (most notably
`lanspread-tauri-deno-ts`) embed it and drive it through a channel-based API.
## Runtime Overview
- `start_peer(game_dir, tx_events, peer_game_db)` boots the asynchronous runtime in the
background and returns an `UnboundedSender<PeerCommand>` that the caller uses
for control. The initial game directory is installed directly into the peer
context, the local library scan is attempted before discovery starts, and the
provided `PeerGameDB` remains shared so the UI layer can observe live peer
metadata.
- `PeerCommand` represents the small control surface exposed to the UI layer:
`ListGames`, `GetGame`, `DownloadGameFiles`, and `SetGameDir`.
- `PeerEvent` enumerates everything the peer runtime reports back to the UI:
library snapshots, download lifecycle updates, and peer membership changes.
- `PeerGameDB` collects remote peer metadata. It aggregates discovered peers
`Game` definitions, tracks the latest ETI version per title, and keeps the
last seen list of `GameFileDescription` entries for each peer.
Internally the peer runtime owns four long-lived tasks that run for the
lifetime of the process:
1. **Server component** (`run_server_component`) listens for QUIC connections,
advertises via mDNS, and serves `Request::ListGames`, `Request::GetGame`,
`Request::GetGameFileData`, and `Request::GetGameFileChunk` by reading from
the local game directory.
2. **Discovery loop** (`run_peer_discovery`) uses the `lanspread-mdns`
helper to discover other peers. The blocking mDNS work is executed on a
dedicated thread via `tokio::task::spawn_blocking` so that the Tokio runtime
remains responsive.
3. **Ping service** (`run_ping_service`) periodically issues QUIC ping requests
to keep peer liveness up to date and prunes stale entries from `PeerGameDB`.
4. **Local game monitor** (`run_local_game_monitor`) periodically rescans the
configured game directory and announces local library deltas to known peers.
`scan_local_library` maintains a lightweight on-disk index and produces both a
`GameDB` and protocol summaries. The resulting database is used to respond to
incoming metadata requests (`Request::ListGames` / `Request::GetGame`).
## Networking and File Transfer
- Transport is handled by [`s2n-quic`](https://github.com/aws/s2n-quic); TLS
cert/key material is compiled in from the repository root.
- Protocol messages are JSON-encoded structures defined in
`lanspread-proto::{Request, Response}`.
- File transfers stream raw bytes over dedicated bidirectional QUIC streams.
`peer::send_game_file_data` sends entire files, while
`peer::send_game_file_chunk` services ranged requests.
### Download Pipeline
When the UI asks to download a game:
1. The UI first issues `PeerCommand::GetGame`. Each peer that still reports the
game is queried via `request_game_details_from_peer`, and their file
manifests are merged inside `PeerGameDB`.
2. Once the UI receives `PeerEvent::GotGameFiles`, it forwards the selected file
list back with `PeerCommand::DownloadGameFiles`.
3. `download_game_files` prepares the filesystem (creating directories and
pre-sizing files where possible), emits `PeerEvent::DownloadGameFilesBegin`,
and builds a per-peer plan (`build_peer_plans`) that round-robins file chunks
across the available peers that advertise the latest version.
4. Each plan is executed in its own task (`download_from_peer`). Chunk requests
use per-chunk QUIC streams and write into pre-created files. The chunk writer
keeps existing data intact and only truncates when we intentionally fall back
to a full file transfer, which prevents corruption when multiple peers fill
different regions of the same file.
5. Failures are accumulated and retried (up to `MAX_RETRY_COUNT`) via
`retry_failed_chunks`. If everything succeeds,
`PeerEvent::DownloadGameFilesFinished` is emitted; otherwise the UI receives
`PeerEvent::DownloadGameFilesFailed`.
## Integration with `lanspread-tauri-deno-ts`
The Tauri application embeds this crate in
`crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs`:
- `LanSpreadState` holds onto the peer control channel, the latest aggregated
`GameDB`, per-game download state, and the user-selected game directory.
- The Tauri commands (`request_games`, `install_game`, `update_game`, and
`update_game_directory`) translate UI actions into `PeerCommand`s. In
particular, `update_game_directory` validates the filesystem path before
storing it, loads the bundled catalog on first use, kicks off the peer runtime
on demand, and mirrors the installed/uninstalled state into the UI-facing
database.
- A background task consumes `PeerEvent`s and fans them out to the front-end via
Tauri publish/subscribe events (`games-list-updated`, `game-download-*`,
`peer-*`). Successful downloads trigger an `unrar` sidecar to unpack ETI
archives and clean up the temporary backup folders that are created when
updates begin.
- When downloads fail the Tauri layer restores the on-disk backup, keeping the
previous installation consistent even after partial transfers.
## Security & Operational Notes
- All QUIC connections are TLS encrypted; the shipped certificates are suitable
for local-network trust but should be rotated for production deployments.
- Peer discovery is restricted to the local link via mDNS.
- Long-running blocking mDNS calls are isolated on dedicated threads which keeps
the async runtime responsive even when discovery takes a long time.
- File writes are chunk-safe: partial chunk downloads now open files without
truncating existing data, avoiding the corruption that occurred previously
when multiple peers collectively filled a file.
## Known Limitations
- `PeerGameDB` currently models the latest metadata that other peers advertise.
If the UI needs to surface titles that only exist locally, additional merging
with the locally scanned `GameDB` will be required.
- The download planner uses a simple round-robin and does not yet take per-peer
throughput or failures into account when distributing work.
Refer to the source (particularly `src/lib.rs`) for the exact message shapes and
state machines.