asd
This commit is contained in:
@@ -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
File diff suppressed because it is too large
Load Diff
+22
-22
@@ -1,14 +1,14 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/lanspread-compat",
|
||||
"crates/lanspread-db",
|
||||
"crates/lanspread-utils",
|
||||
"crates/lanspread-mdns",
|
||||
"crates/lanspread-proto",
|
||||
"crates/lanspread-peer",
|
||||
"crates/lanspread-tauri-deno-ts/src-tauri",
|
||||
]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/lanspread-compat",
|
||||
"crates/lanspread-db",
|
||||
"crates/lanspread-mdns",
|
||||
"crates/lanspread-peer",
|
||||
"crates/lanspread-proto",
|
||||
"crates/lanspread-tauri-deno-ts/src-tauri",
|
||||
"crates/lanspread-utils",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
base64 = "0.22"
|
||||
@@ -18,24 +18,24 @@ 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"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"derive",
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
"derive",
|
||||
"runtime-tokio",
|
||||
"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"] }
|
||||
@@ -44,25 +44,25 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uuid = { version = "1", features = ["v7"] }
|
||||
walkdir = "2"
|
||||
windows = { version = "0.62", features = [
|
||||
"Win32",
|
||||
"Win32_UI",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32",
|
||||
"Win32_UI",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
|
||||
[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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 downloading = ctx.downloading_games.read().await;
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user