feat(peer): prototype streamed installs
Add a streamed-install prototype that can receive archive-derived install bytes straight into local/ without first storing the peer-owned root archive payload. This is intended for low-disk clients that want to install a game but opt out of becoming a downloadable peer source for that game. The protocol gains a current-version-only StreamInstall request and framed StreamInstallFrame responses. The peer core owns the generic transport, transaction, path validation, size checks, CRC32 verification, and lifecycle state. The archive-specific work is hidden behind StreamInstallProvider so the prototype can use unrar while the final implementation can swap in a better provider without rewriting the peer command path. The receiver writes into .local.installing and only promotes to local/ after the full stream verifies. It deliberately does not write the root version.ini or archive files, so the settled local state is installed=true, downloaded=false, and availability=LocalOnly. That preserves the existing rule that local/ is not served to peers and makes streamed receivers non-sources by construction. The CLI is the only caller for now. It exposes stream-install and provides the prototype unrar implementation with unrar lt for entry metadata and unrar p for file bytes. This is simple and good enough to prove non-solid archive streaming, but it is not the production provider shape for solid archives because per-file unrar p would repeatedly decompress prefixes. The Tauri app explicitly passes stream_install_provider: None, so the GUI behavior stays unchanged until a real product path is designed. Document the production-readiness work in NEXT_STEPS.md. The main follow-up is to make the provider abstraction final-ish and replace the per-file CLI unrar provider with a one-pass archive provider, then wire a deliberate GUI low-disk mode, retry semantics, and broader failure scenarios. Test Plan: - just fmt - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just test - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py \ S39 S40 --build-image - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just clippy - git diff --check - git diff --cached --check Follow-up: NEXT_STEPS.md
This commit is contained in:
@@ -6,7 +6,14 @@ use lanspread_db::db::{GameCatalog, GameDB};
|
||||
use tokio::sync::{RwLock, mpsc::UnboundedSender};
|
||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||
|
||||
use crate::{PeerEvent, Unpacker, events, library::LocalLibraryState, peer_db::PeerGameDB};
|
||||
use crate::{
|
||||
PeerEvent,
|
||||
StreamInstallProvider,
|
||||
Unpacker,
|
||||
events,
|
||||
library::LocalLibraryState,
|
||||
peer_db::PeerGameDB,
|
||||
};
|
||||
|
||||
/// Thread-safe map of active outbound file transfers grouped by game ID.
|
||||
pub type OutboundTransfers = Arc<RwLock<HashMap<String, Vec<(u64, CancellationToken)>>>>;
|
||||
@@ -38,6 +45,7 @@ pub struct Ctx {
|
||||
pub active_operations: Arc<RwLock<HashMap<String, OperationKind>>>,
|
||||
pub active_downloads: Arc<RwLock<HashMap<String, CancellationToken>>>,
|
||||
pub unpacker: Arc<dyn Unpacker>,
|
||||
pub stream_install_provider: Arc<dyn StreamInstallProvider>,
|
||||
pub catalog: Arc<RwLock<GameCatalog>>,
|
||||
pub peer_id: Arc<String>,
|
||||
pub shutdown: CancellationToken,
|
||||
@@ -57,6 +65,7 @@ pub struct PeerCtx {
|
||||
pub catalog: Arc<RwLock<GameCatalog>>,
|
||||
pub peer_id: Arc<String>,
|
||||
pub tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
||||
pub stream_install_provider: Arc<dyn StreamInstallProvider>,
|
||||
pub shutdown: CancellationToken,
|
||||
pub task_tracker: TaskTracker,
|
||||
pub active_outbound_transfers: OutboundTransfers,
|
||||
@@ -86,6 +95,7 @@ impl Ctx {
|
||||
task_tracker: TaskTracker,
|
||||
catalog: Arc<RwLock<GameCatalog>>,
|
||||
active_outbound_transfers: OutboundTransfers,
|
||||
stream_install_provider: Arc<dyn StreamInstallProvider>,
|
||||
) -> Self {
|
||||
Self {
|
||||
game_dir: Arc::new(RwLock::new(game_dir)),
|
||||
@@ -97,6 +107,7 @@ impl Ctx {
|
||||
active_operations: Arc::new(RwLock::new(HashMap::new())),
|
||||
active_downloads: Arc::new(RwLock::new(HashMap::new())),
|
||||
unpacker,
|
||||
stream_install_provider,
|
||||
catalog,
|
||||
peer_id: Arc::new(peer_id),
|
||||
shutdown,
|
||||
@@ -120,6 +131,7 @@ impl Ctx {
|
||||
catalog: self.catalog.clone(),
|
||||
peer_id: self.peer_id.clone(),
|
||||
tx_notify_ui,
|
||||
stream_install_provider: self.stream_install_provider.clone(),
|
||||
shutdown: self.shutdown.clone(),
|
||||
task_tracker: self.task_tracker.clone(),
|
||||
active_outbound_transfers: self.active_outbound_transfers.clone(),
|
||||
|
||||
Reference in New Issue
Block a user