This commit is contained in:
2026-02-26 20:12:25 +01:00
parent 4318927060
commit 86d0f93ede
9 changed files with 576 additions and 758 deletions
+5
View File
@@ -78,15 +78,18 @@ LanSpread follows a layered, modular architecture with clear separation of conce
The project is organized as a Cargo workspace with 7 crates:
**Foundation Layer:**
- **lanspread-utils**: Utility macros and helpers used across all crates
- **lanspread-db**: Core data structures (`Game`, `GameFileDescription`, game metadata)
**Protocol & Communication:**
- **lanspread-proto**: P2P communication protocol definitions
- Message types: `Request` (Ping, ListGames, GetGame, etc.), `Response`, `Message` trait
- Serialization: JSON with `serde`
**Network Discovery & Compatibility:**
- **lanspread-mdns**: mDNS service discovery and advertisement
- Advertises and discovers "_lanspread._udp.local." services on LAN
- Uses `mdns-sd` crate
@@ -96,6 +99,7 @@ The project is organized as a Cargo workspace with 7 crates:
- Converts `EtiGame` structs to modern `Game` struct
**Core P2P Engine:**
- **lanspread-peer**: Central orchestration for all P2P functionality
- Entry point: `start_peer()` in `lib.rs`
- Communicates via unbounded channels (`PeerEvent`, `PeerCommand`)
@@ -108,6 +112,7 @@ The project is organized as a Cargo workspace with 7 crates:
- `path_validation.rs`: Security for file operations
**Application Layer:**
- **lanspread-tauri-deno-ts**: Tauri desktop application
- Binary + Library crate for the UI layer
- IPC commands: `request_games`, `install_game`, etc.
Generated
+501 -693
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -1,14 +1,14 @@
[workspace]
resolver = "2"
members = [
"crates/lanspread-compat",
"crates/lanspread-db",
"crates/lanspread-utils",
"crates/lanspread-mdns",
"crates/lanspread-proto",
"crates/lanspread-peer",
"crates/lanspread-proto",
"crates/lanspread-tauri-deno-ts/src-tauri",
"crates/lanspread-utils",
]
resolver = "2"
[workspace.dependencies]
base64 = "0.22"
@@ -18,10 +18,10 @@ clap = { version = "4", features = ["derive"] }
eyre = "0.6"
futures = "0.3"
gethostname = "1"
if-addrs = "0.11"
if-addrs = "0.15"
itertools = "0.14"
log = "0.4"
mdns-sd = "0.17"
mdns-sd = "0.18"
mimalloc = { version = "0.1", features = ["secure"] }
s2n-quic = { version = "1", features = ["provider-event-tracing"] }
semver = "1"
@@ -33,9 +33,9 @@ sqlx = { version = "0.8", default-features = false, features = [
"sqlite",
] }
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2"
tauri-plugin-log = "2"
tauri-plugin-shell = "2"
tauri-plugin-dialog = "2"
tauri-plugin-store = "2"
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] }
@@ -52,17 +52,17 @@ windows = { version = "0.62", features = [
[profile.release]
debug = true
strip = false
debug-assertions = true
overflow-checks = true
strip = false
lto = false
panic = "unwind"
codegen-units = 1
[profile.release-lto]
inherits = "release"
lto = true
debug = false
strip = true
debug-assertions = false
overflow-checks = false
strip = true
lto = true
-15
View File
@@ -1,15 +0,0 @@
# Wrong decisions
- **tauri-leptos** adds unnecessary complexity to the project.
Tauri is built to transfer backend Rust stuff to the frontend JavaScript world.
But with leptos, the frontend becomes Rust so you have to transfer everything again back into the Rust world.
# Good decisions
- **Tauri** is strong 💪
- Easy project setup with `cargo install create-tauri-app --locked` and `cargo create-tauri-app`
- Easy testing with `cargo tauri dev`
- Easy bundling (with installers and everything) with `cargo tauri build`
- Final binary size of my tauri-leptos project is 13MB (which seems small to me if you think that a whole WASM / Web stack is in there)
# Open questions
- **logging**: I don't understand the relationship (or lack thereof) between `tracing` and `log`.
I had to refactor logging in the client away from `tracing`.
+4 -2
View File
@@ -4,10 +4,10 @@
Simple server and GUI for LAN parties.
## Development
### Prerequisites
```bash
# install Tauri CLI
cargo install tauri-cli
@@ -15,9 +15,10 @@ cargo install tauri-cli
# install Deno with a package manager or from https://deno.land/
```
### Build
#### Frontend
```bash
# Development
cargo tauri dev # prefix with RUST_LOG=your_module=debug or similary for more verbose output
@@ -36,6 +37,7 @@ deno outdated --update --latest
```
#### Backend
```bash
# Development
./server.sh [options...] # prefix with RUST_LOG=your_module=debug or similary for more verbose output
+30 -17
View File
@@ -95,29 +95,42 @@ Most scans become O(number of game dirs), with full recursion only when needed.
- Any mismatch or missing delta falls back to `LibrarySnapshot`.
- Loss of goodbye is harmless; stale timeout is authoritative.
## TODO: roadmap from current design to this one
## Roadmap from current design to this one
1. Protocol updates in `lanspread-proto`:
- Add `Hello`, `HelloAck`, `LibrarySummary`, `LibrarySnapshot`,
`LibraryDelta`, and optional `Goodbye`.
- Add `peer_id`, `library_rev`, and `manifest_hash` to relevant types.
- Define `Hello`, `HelloAck`, `LibrarySummary`, `LibrarySnapshot`,
`LibraryDelta`, and optional `Goodbye` messages.
- Thread `peer_id`, `library_rev`, and `manifest_hash` through all
library and manifest-bearing types.
- Make `HelloAck` carry the remote `library_rev` and `manifest_hash`
so the client can immediately select `LibraryDelta` vs `LibrarySnapshot`.
2. Peer identity:
- Introduce stable `peer_id` in `PeerInfo` and `PeerGameDB`.
- Map `peer_id` to current `SocketAddr` and update on IP changes.
- Persist a stable `peer_id` (UUID) in the peer config and inject it into
`PeerInfo` and `PeerGameDB` at startup.
- Track `peer_id -> SocketAddr` in the discovery table and update the
address on any incoming handshake or mDNS refresh.
3. Discovery handshake:
- Advertise TXT records in mDNS.
- Add handshake in `run_peer_discovery` or connection setup.
- Keep compatibility fallback to `ListGames` for older peers.
- Publish `peer_id` and `library_rev` in mDNS TXT records to avoid
immediate TCP/QUIC roundtrips when nothing changed.
- Add a lightweight handshake in `run_peer_discovery` that exchanges
`Hello`/`HelloAck` before any library sync.
- Keep a fallback path that uses `ListGames` when `Hello` is unsupported.
4. Library revisioning:
- Track `library_rev` locally.
- Apply `LibraryDelta` and reject stale revisions.
- Use `LibrarySnapshot` for first sync or delta mismatch.
- Store a monotonic `library_rev` locally and increment only after a
successful index refresh completes.
- Apply `LibraryDelta` when `library_rev` matches; reject stale or future
revisions and request `LibrarySnapshot` instead.
- Cache the last accepted `manifest_hash` per peer to short-circuit
manifest requests when unchanged.
5. Local index + scan optimizations:
- Add cached index storage (e.g., `.lanspread/index.json`).
- Implement filesystem watchers with debounce.
- Add a low-frequency full scan as a safety net.
- Introduce a cached index file (e.g., `.lanspread/index.json`) that stores
per-root fingerprints and computed manifests.
- Use filesystem watchers with a debounce window to collect changes and
incrementally update the cache.
- Schedule a low-frequency full scan to reconcile missed watcher events.
6. Announce updates:
- Replace broad `AnnounceGames` with deltas.
- Send `LibrarySummary` on new connections.
- Replace `AnnounceGames` with `LibraryDelta` broadcasts keyed by
`library_rev`.
- Send `LibrarySummary` on new connections to seed the delta flow.
7. File manifest caching:
- Store per-game `manifest_hash` and only fetch details when changed.
8. Liveness:
+6 -7
View File
@@ -11,10 +11,7 @@ use crate::{
download::download_game_files,
identity::FEATURE_LIBRARY_DELTA,
local_games::{
LocalLibraryScan,
get_game_file_descriptions,
local_download_available,
scan_local_library,
LocalLibraryScan, get_game_file_descriptions, local_download_available, scan_local_library,
},
network::{announce_games_to_peer, request_game_details_from_peer, send_library_delta},
peer_db::{PeerGameDB, PeerId},
@@ -218,10 +215,13 @@ pub async fn handle_download_game_files_command(
return;
}
let local_dl_available = {
let downloading = ctx.downloading_games.read().await;
local_download_available(&games_folder, &id, &downloading).await
};
if peer_whitelist.is_empty() {
if local_download_available(&games_folder, &id, &downloading).await {
drop(downloading);
if local_dl_available {
log::info!("Using locally downloaded files for game {id}; skipping peer transfer");
if let Err(e) = tx_notify_ui.send(PeerEvent::DownloadGameFilesBegin { id: id.clone() })
{
@@ -237,7 +237,6 @@ pub async fn handle_download_game_files_command(
}
return;
}
drop(downloading);
{
let mut in_progress = ctx.downloading_games.write().await;
@@ -280,6 +280,8 @@ async fn get_game_thumbnail(
tauri::path::BaseDirectory::Resource,
)?;
dbg!(&resource_path);
let image_data = std::fs::read(&resource_path)?;
let base64_data = base64::engine::general_purpose::STANDARD.encode(&image_data);
Ok(format!("data:image/jpeg;base64,{base64_data}"))
@@ -854,7 +856,7 @@ pub fn run() {
.path()
.resolve("game.db", tauri::path::BaseDirectory::Resource)
{
Ok(path) => path,
Ok(path) => { dbg!(&path); path },
Err(e) => {
log::error!("Failed to resolve game.db resource: {e}");
panic!("game.db resource is mandatory - cannot continue");
+4
View File
@@ -1,4 +1,5 @@
export RUSTFLAGS := "-C target-cpu=native"
export WEBKIT_DISABLE_COMPOSITING_MODE := "1"
server:
cargo build -p lanspread-server
@@ -6,6 +7,9 @@ server:
client:
cargo tauri dev
buildclient:
cargo tauri build --no-bundle -- --profile dev
fmt:
cargo +nightly fmt