From a1013b028d3ac316a1088153b93b63626d212ad5 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 9 Aug 2026 17:59:01 +0200 Subject: [PATCH] refactor(peer): centralize game root path policy Game scanning, manifest validation, install recovery, migration, and download cleanup each carried their own spellings and case rules for reserved entries. Those copies had already diverged, which made it possible for one subsystem to accept or expose a path that another treated as application-owned state. Introduce one game_paths module for the canonical names and conservative portable comparison policy. Keep context-specific predicates for manifest and scanner protection versus cancellation preservation: cancellation still sweeps its own version transaction scratch files, while install and migration state survive. Reuse the constants for all production path construction sites. Test Plan: - `just test` -- passed (175 lanspread-peer tests and full workspace) - `just clippy` -- passed - `just fmt` -- Rust, TOML, and Prettier completed; the recipe remains blocked by 39 pre-existing rumdl issues in five unrelated Markdown files - `git diff --cached --check` -- passed --- .../lanspread-peer/src/download/manifest.rs | 29 +--- crates/lanspread-peer/src/download/storage.rs | 6 +- .../src/download/version_ini.rs | 20 +-- crates/lanspread-peer/src/game_paths.rs | 125 ++++++++++++++++++ crates/lanspread-peer/src/install/intent.rs | 9 +- crates/lanspread-peer/src/install/remove.rs | 5 +- .../lanspread-peer/src/install/transaction.rs | 22 +-- crates/lanspread-peer/src/launch_settings.rs | 3 +- crates/lanspread-peer/src/lib.rs | 1 + crates/lanspread-peer/src/local_games.rs | 67 +++------- crates/lanspread-peer/src/migration.rs | 17 ++- .../src/services/local_monitor.rs | 20 +-- crates/lanspread-peer/src/services/stream.rs | 3 +- 13 files changed, 198 insertions(+), 129 deletions(-) create mode 100644 crates/lanspread-peer/src/game_paths.rs diff --git a/crates/lanspread-peer/src/download/manifest.rs b/crates/lanspread-peer/src/download/manifest.rs index fecc0dc..8d304b2 100644 --- a/crates/lanspread-peer/src/download/manifest.rs +++ b/crates/lanspread-peer/src/download/manifest.rs @@ -7,6 +7,8 @@ use std::{ use eyre::WrapErr; use lanspread_db::db::{GameCatalog, GameFileDescription}; +use crate::game_paths::{VERSION_INI, is_download_protected_root_name, portable_name_key}; + /// A remote manifest may describe at most this many filesystem entries. pub(crate) const MAX_DOWNLOAD_MANIFEST_ENTRIES: usize = 100_000; /// A single remotely described file may be at most one tebibyte. @@ -21,8 +23,6 @@ pub(crate) const MAX_DOWNLOAD_COMPONENT_BYTES: usize = 255; pub(crate) const MAX_DOWNLOAD_RELATIVE_PATH_BYTES: usize = 900; const MAX_DOWNLOAD_DESTINATION_UNITS: usize = 1_000; -const VERSION_INI: &str = "version.ini"; - /// One entry whose path and shape were validated as part of a complete manifest. #[derive(Clone, Debug)] pub(crate) struct ValidatedDownloadEntry { @@ -259,7 +259,7 @@ impl<'a> ProtocolV7ManifestBuilder<'a> { if windows_alias_component(root_component)? == self.game_alias { eyre::bail!("download path contains a doubled game prefix: {display_path}"); } - if is_protected_root_component(root_component) { + if is_download_protected_root_name(root_component) { eyre::bail!("download path targets install or recovery state: {display_path}"); } Ok(()) @@ -337,7 +337,7 @@ fn validate_game_id(game_id: &str) -> eyre::Result<()> { eyre::bail!("catalog game ID must be one path component: {game_id}"); } validate_component(game_id)?; - if is_protected_root_component(game_id) { + if is_download_protected_root_name(game_id) { eyre::bail!("catalog game ID is reserved for application state: {game_id}"); } Ok(()) @@ -447,7 +447,7 @@ fn validate_component(component: &str) -> eyre::Result<()> { fn windows_alias_component(component: &str) -> eyre::Result { validate_component(component)?; - Ok(component.to_uppercase()) + Ok(portable_name_key(component)) } fn is_windows_device_name(stem: &str) -> bool { @@ -472,25 +472,6 @@ fn looks_like_dos_short_name(component: &str) -> bool { }) } -fn is_protected_root_component(component: &str) -> bool { - let alias = component.to_uppercase(); - alias == "LOCAL" - || alias.starts_with(".LOCAL.") - || alias.starts_with(".VERSION.INI.") - || matches!( - alias.as_str(), - ".SYNC" - | ".LANSPREAD" - | ".LANSPREAD.JSON" - | ".LANSPREAD.JSON.TMP" - | ".LANSPREAD_OWNED" - | ".SOFTLAN_FIRST_START_DONE" - | ".SOFTLAN_GAME_INSTALLED" - | "INSTALL_INTENT.JSON" - | "INSTALL_INTENT.JSON.TMP" - ) -} - fn validate_entry_shape( shapes: &mut BTreeMap, alias_path: &str, diff --git a/crates/lanspread-peer/src/download/storage.rs b/crates/lanspread-peer/src/download/storage.rs index d8c7d70..8c0930d 100644 --- a/crates/lanspread-peer/src/download/storage.rs +++ b/crates/lanspread-peer/src/download/storage.rs @@ -3,9 +3,7 @@ use std::{io::ErrorKind, path::Path}; use tokio::fs::OpenOptions; use super::manifest::ValidatedDownloadManifest; -use crate::local_games::is_local_dir_name; - -const SYNC_DIR: &str = ".sync"; +use crate::game_paths::is_preserved_on_download_discard; /// Prepares storage for game files by creating directories and pre-allocating files. pub(super) async fn prepare_game_storage(manifest: &ValidatedDownloadManifest) -> eyre::Result<()> { @@ -90,7 +88,7 @@ pub(super) async fn discard_cancelled_download( } fn should_preserve_on_download_discard(name: &str) -> bool { - is_local_dir_name(name) || name.starts_with(".local.") || name == SYNC_DIR + is_preserved_on_download_discard(name) } async fn remove_entry(path: &Path) -> eyre::Result<()> { diff --git a/crates/lanspread-peer/src/download/version_ini.rs b/crates/lanspread-peer/src/download/version_ini.rs index 6d703f8..b4861f0 100644 --- a/crates/lanspread-peer/src/download/version_ini.rs +++ b/crates/lanspread-peer/src/download/version_ini.rs @@ -3,6 +3,8 @@ use std::path::Path; use lanspread_db::db::GameFileDescription; use tokio::{io::AsyncWriteExt, sync::Mutex}; +use crate::game_paths::{VERSION_DISCARDED_FILE, VERSION_INI, VERSION_TMP_FILE}; + #[derive(Debug)] pub(super) struct VersionIniBuffer { relative_path: String, @@ -48,27 +50,27 @@ impl VersionIniBuffer { pub(super) async fn begin_version_ini_transaction(game_root: &Path) -> eyre::Result<()> { tokio::fs::create_dir_all(game_root).await?; - remove_file_if_exists(&game_root.join(".version.ini.tmp")).await?; - remove_file_if_exists(&game_root.join(".version.ini.discarded")).await?; + remove_file_if_exists(&game_root.join(VERSION_TMP_FILE)).await?; + remove_file_if_exists(&game_root.join(VERSION_DISCARDED_FILE)).await?; - let version_path = game_root.join("version.ini"); + let version_path = game_root.join(VERSION_INI); if tokio::fs::metadata(&version_path) .await .is_ok_and(|metadata| metadata.is_file()) { - tokio::fs::rename(version_path, game_root.join(".version.ini.discarded")).await?; + tokio::fs::rename(version_path, game_root.join(VERSION_DISCARDED_FILE)).await?; } Ok(()) } pub(super) async fn rollback_version_ini_transaction(game_root: &Path) { - if let Err(err) = remove_file_if_exists(&game_root.join(".version.ini.tmp")).await { + if let Err(err) = remove_file_if_exists(&game_root.join(VERSION_TMP_FILE)).await { log::warn!( "Failed to sweep partial version.ini tmp in {}: {err}", game_root.display() ); } - if let Err(err) = remove_file_if_exists(&game_root.join(".version.ini.discarded")).await { + if let Err(err) = remove_file_if_exists(&game_root.join(VERSION_DISCARDED_FILE)).await { log::warn!( "Failed to sweep discarded version.ini in {}: {err}", game_root.display() @@ -80,8 +82,8 @@ pub(super) async fn commit_version_ini_buffer( game_root: &Path, buffer: &VersionIniBuffer, ) -> eyre::Result<()> { - let tmp_path = game_root.join(".version.ini.tmp"); - let version_path = game_root.join("version.ini"); + let tmp_path = game_root.join(VERSION_TMP_FILE); + let version_path = game_root.join(VERSION_INI); let bytes = buffer.snapshot().await; let mut file = tokio::fs::File::create(&tmp_path).await?; @@ -91,7 +93,7 @@ pub(super) async fn commit_version_ini_buffer( tokio::fs::rename(&tmp_path, &version_path).await?; sync_parent_dir(&version_path)?; - remove_file_if_exists(&game_root.join(".version.ini.discarded")).await?; + remove_file_if_exists(&game_root.join(VERSION_DISCARDED_FILE)).await?; Ok(()) } diff --git a/crates/lanspread-peer/src/game_paths.rs b/crates/lanspread-peer/src/game_paths.rs new file mode 100644 index 0000000..fda8a3f --- /dev/null +++ b/crates/lanspread-peer/src/game_paths.rs @@ -0,0 +1,125 @@ +//! Shared names and ownership policy for entries below a game root. + +pub(crate) const LOCAL_DIR: &str = "local"; +pub(crate) const INSTALLING_DIR: &str = ".local.installing"; +pub(crate) const BACKUP_DIR: &str = ".local.backup"; +pub(crate) const INSTALL_OWNED_MARKER: &str = ".lanspread_owned"; +pub(crate) const VERSION_INI: &str = "version.ini"; +pub(crate) const VERSION_TMP_FILE: &str = ".version.ini.tmp"; +pub(crate) const VERSION_DISCARDED_FILE: &str = ".version.ini.discarded"; +pub(crate) const LEGACY_LIBRARY_INDEX_DIR: &str = ".lanspread"; +pub(crate) const LEGACY_INTENT_FILE: &str = ".lanspread.json"; +pub(crate) const LEGACY_INTENT_TMP_FILE: &str = ".lanspread.json.tmp"; +pub(crate) const LEGACY_FIRST_START_DONE_FILE: &str = ".softlan_first_start_done"; +pub(crate) const LEGACY_SOFTLAN_INSTALL_MARKER: &str = ".softlan_game_installed"; +pub(crate) const INSTALL_INTENT_FILE: &str = "install_intent.json"; +pub(crate) const INSTALL_INTENT_TMP_FILE: &str = "install_intent.json.tmp"; + +/// Returns the conservative cross-platform comparison key used for reserved names. +pub(crate) fn portable_name_key(name: &str) -> String { + name.to_uppercase() +} + +/// Matches the committed install directory according to the host filesystem. +#[cfg(target_os = "windows")] +pub(crate) fn is_local_dir_name(name: &str) -> bool { + name.eq_ignore_ascii_case(LOCAL_DIR) +} + +/// Matches the committed install directory according to the host filesystem. +#[cfg(not(target_os = "windows"))] +pub(crate) fn is_local_dir_name(name: &str) -> bool { + name == LOCAL_DIR +} + +/// Returns whether a top-level entry belongs to install, recovery, or legacy state. +/// +/// This deliberately uses a conservative platform-independent comparison because +/// a manifest accepted on one peer may be materialized on another operating system. +pub(crate) fn is_download_protected_root_name(name: &str) -> bool { + let key = portable_name_key(name); + key == "LOCAL" + || key.starts_with(".LOCAL.") + || key.starts_with(".VERSION.INI.") + || matches!( + key.as_str(), + ".SYNC" + | ".LANSPREAD" + | ".LANSPREAD.JSON" + | ".LANSPREAD.JSON.TMP" + | ".LANSPREAD_OWNED" + | ".SOFTLAN_FIRST_START_DONE" + | ".SOFTLAN_GAME_INSTALLED" + | "INSTALL_INTENT.JSON" + | "INSTALL_INTENT.JSON.TMP" + ) +} + +/// Returns whether cancellation cleanup must preserve an application-owned entry. +/// +/// Version transaction scratch files are intentionally excluded: they belong to +/// the cancelled download and are safe to sweep. Install and migration state is +/// independent of that download and must survive it. +pub(crate) fn is_preserved_on_download_discard(name: &str) -> bool { + let key = portable_name_key(name); + key == "LOCAL" + || key.starts_with(".LOCAL.") + || matches!( + key.as_str(), + ".SYNC" + | ".LANSPREAD" + | ".LANSPREAD.JSON" + | ".LANSPREAD.JSON.TMP" + | ".LANSPREAD_OWNED" + | ".SOFTLAN_FIRST_START_DONE" + | ".SOFTLAN_GAME_INSTALLED" + | "INSTALL_INTENT.JSON" + | "INSTALL_INTENT.JSON.TMP" + ) +} + +/// Returns whether an entry in the configured games directory is application state. +pub(crate) fn is_ignored_games_root_name(name: &str) -> bool { + portable_name_key(name) == ".LANSPREAD" +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn protected_policy_covers_current_and_legacy_state() { + for name in [ + LOCAL_DIR, + "LOCAL", + INSTALLING_DIR, + BACKUP_DIR, + VERSION_TMP_FILE, + VERSION_DISCARDED_FILE, + ".sync", + LEGACY_LIBRARY_INDEX_DIR, + LEGACY_INTENT_FILE, + LEGACY_INTENT_TMP_FILE, + INSTALL_OWNED_MARKER, + LEGACY_FIRST_START_DONE_FILE, + LEGACY_SOFTLAN_INSTALL_MARKER, + INSTALL_INTENT_FILE, + INSTALL_INTENT_TMP_FILE, + ".ſync", + ] { + assert!(is_download_protected_root_name(name), "missed {name}"); + } + assert!(!is_download_protected_root_name(VERSION_INI)); + assert!(!is_download_protected_root_name("archive.eti")); + } + + #[test] + fn cancellation_policy_sweeps_only_download_transaction_state() { + assert!(is_preserved_on_download_discard(LOCAL_DIR)); + assert!(is_preserved_on_download_discard(BACKUP_DIR)); + assert!(is_preserved_on_download_discard(LEGACY_INTENT_FILE)); + assert!(!is_preserved_on_download_discard(VERSION_INI)); + assert!(!is_preserved_on_download_discard(VERSION_TMP_FILE)); + assert!(!is_preserved_on_download_discard("archive.eti")); + } +} diff --git a/crates/lanspread-peer/src/install/intent.rs b/crates/lanspread-peer/src/install/intent.rs index 77d281d..cfabfc7 100644 --- a/crates/lanspread-peer/src/install/intent.rs +++ b/crates/lanspread-peer/src/install/intent.rs @@ -6,11 +6,12 @@ use std::{ use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; +use crate::game_paths::{ + INSTALL_INTENT_FILE as INTENT_FILE, + INSTALL_INTENT_TMP_FILE as INTENT_TMP_FILE, +}; + const INTENT_SCHEMA_VERSION: u32 = 1; -pub(crate) const LEGACY_INTENT_FILE: &str = ".lanspread.json"; -pub(crate) const LEGACY_INTENT_TMP_FILE: &str = ".lanspread.json.tmp"; -const INTENT_FILE: &str = "install_intent.json"; -const INTENT_TMP_FILE: &str = "install_intent.json.tmp"; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum InstallIntentState { diff --git a/crates/lanspread-peer/src/install/remove.rs b/crates/lanspread-peer/src/install/remove.rs index 927ee70..14b11f2 100644 --- a/crates/lanspread-peer/src/install/remove.rs +++ b/crates/lanspread-peer/src/install/remove.rs @@ -6,10 +6,7 @@ use std::{ use eyre::{WrapErr, bail}; -const LOCAL_DIR: &str = "local"; -const INSTALLING_DIR: &str = ".local.installing"; -const BACKUP_DIR: &str = ".local.backup"; -const VERSION_INI: &str = "version.ini"; +use crate::game_paths::{BACKUP_DIR, INSTALLING_DIR, LOCAL_DIR, VERSION_INI}; /// Remove the downloaded files for an uninstalled game root. /// diff --git a/crates/lanspread-peer/src/install/transaction.rs b/crates/lanspread-peer/src/install/transaction.rs index eb9fc66..e6d5719 100644 --- a/crates/lanspread-peer/src/install/transaction.rs +++ b/crates/lanspread-peer/src/install/transaction.rs @@ -11,14 +11,18 @@ use super::{ intent::{InstallIntent, InstallIntentState, read_intent, write_intent}, unpack::Unpacker, }; -use crate::{local_games::version_ini_is_regular_file, state_paths::launch_settings_applied_path}; - -const LOCAL_DIR: &str = "local"; -const INSTALLING_DIR: &str = ".local.installing"; -const BACKUP_DIR: &str = ".local.backup"; -const OWNED_MARKER: &str = ".lanspread_owned"; -const VERSION_TMP_FILE: &str = ".version.ini.tmp"; -const VERSION_DISCARDED_FILE: &str = ".version.ini.discarded"; +use crate::{ + game_paths::{ + BACKUP_DIR, + INSTALL_OWNED_MARKER, + INSTALLING_DIR, + LOCAL_DIR, + VERSION_DISCARDED_FILE, + VERSION_TMP_FILE, + }, + local_games::version_ini_is_regular_file, + state_paths::launch_settings_applied_path, +}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum FsEntryState { @@ -672,7 +676,7 @@ fn backup_dir(game_root: &Path) -> PathBuf { } fn owned_marker(path: &Path) -> PathBuf { - path.join(OWNED_MARKER) + path.join(INSTALL_OWNED_MARKER) } impl From for FsEntryState { diff --git a/crates/lanspread-peer/src/launch_settings.rs b/crates/lanspread-peer/src/launch_settings.rs index 85dd83c..07e1f2f 100644 --- a/crates/lanspread-peer/src/launch_settings.rs +++ b/crates/lanspread-peer/src/launch_settings.rs @@ -20,9 +20,8 @@ use std::{ use eyre::WrapErr; -use crate::state_paths::launch_settings_applied_path; +use crate::{game_paths::LOCAL_DIR, state_paths::launch_settings_applied_path}; -const LOCAL_DIR: &str = "local"; const ACCOUNT_NAME_FILE: &str = "account_name.txt"; const LANGUAGE_FILE: &str = "language.txt"; const SMART_STEAM_EMU_INI: &str = "SmartSteamEmu.ini"; diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index d510632..8d1747f 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -18,6 +18,7 @@ mod context; mod download; mod error; mod events; +mod game_paths; mod handlers; mod identity; mod install; diff --git a/crates/lanspread-peer/src/local_games.rs b/crates/lanspread-peer/src/local_games.rs index 31d8c0c..fbdaf10 100644 --- a/crates/lanspread-peer/src/local_games.rs +++ b/crates/lanspread-peer/src/local_games.rs @@ -14,25 +14,25 @@ use lanspread_proto::{Availability, GameSummary}; use serde::{Deserialize, Serialize}; use tokio::{io::AsyncWriteExt, sync::Mutex}; -use crate::{context::OperationKind, error::PeerError}; +use crate::{ + context::OperationKind, + error::PeerError, + game_paths::{ + LEGACY_LIBRARY_INDEX_DIR, + LOCAL_DIR, + VERSION_INI, + is_download_protected_root_name, + is_ignored_games_root_name, + }, +}; // ============================================================================= // Local directory helpers // ============================================================================= -#[cfg(target_os = "windows")] -pub fn is_local_dir_name(name: &str) -> bool { - name.eq_ignore_ascii_case("local") -} - -#[cfg(not(target_os = "windows"))] -pub fn is_local_dir_name(name: &str) -> bool { - name == "local" -} - /// Checks if `local/` is a committed install directory. pub async fn local_dir_is_directory(path: &Path) -> bool { - let local_dir = path.join("local"); + let local_dir = path.join(LOCAL_DIR); tokio::fs::metadata(&local_dir) .await .is_ok_and(|metadata| metadata.is_dir()) @@ -40,7 +40,7 @@ pub async fn local_dir_is_directory(path: &Path) -> bool { /// Checks if the root-level `version.ini` sentinel exists as a regular file. pub async fn version_ini_is_regular_file(game_path: &Path) -> bool { - let version_path = game_path.join("version.ini"); + let version_path = game_path.join(VERSION_INI); tokio::fs::metadata(&version_path) .await .is_ok_and(|metadata| metadata.is_file()) @@ -105,11 +105,7 @@ pub async fn local_download_matches_catalog( // Local library index and scanning // ============================================================================= -const LEGACY_LIBRARY_INDEX_DIR: &str = ".lanspread"; const LIBRARY_INDEX_FILE: &str = "library_index.json"; -const INTENT_LOG_FILE: &str = ".lanspread.json"; -const VERSION_TMP_FILE: &str = ".version.ini.tmp"; -const VERSION_DISCARDED_FILE: &str = ".version.ini.discarded"; static LIBRARY_INDEX_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); @@ -289,7 +285,7 @@ async fn root_eti_fingerprints(game_path: &Path) -> eyre::Result eyre::Result { let eti_files = root_eti_fingerprints(game_path).await?; - let version_path = game_path.join("version.ini"); + let version_path = game_path.join(VERSION_INI); let (version_mtime, version_contents) = match tokio::fs::metadata(&version_path).await { Ok(metadata) if metadata.is_file() => { let contents = match tokio::fs::read_to_string(&version_path).await { @@ -317,40 +313,15 @@ async fn fingerprint_game_dir(game_path: &Path) -> eyre::Result }) } -pub fn is_ignored_game_root_name(name: &str) -> bool { - name == LEGACY_LIBRARY_INDEX_DIR -} - -fn is_reserved_transient_name(name: &str) -> bool { - name.starts_with(".local.") - || name == VERSION_TMP_FILE - || name == VERSION_DISCARDED_FILE - || name == INTENT_LOG_FILE - || name == LEGACY_LIBRARY_INDEX_DIR -} - fn should_skip_root_entry(entry: &walkdir::DirEntry) -> bool { if entry.depth() != 1 { return false; } - if entry.file_type().is_dir() && entry.file_name().to_str().is_some_and(is_local_dir_name) { - return true; - } - - if let Some(name) = entry.file_name().to_str() { - if is_reserved_transient_name(name) { - return true; - } - if entry.file_type().is_dir() && name == ".sync" { - return true; - } - if entry.file_type().is_file() && name == ".softlan_game_installed" { - return true; - } - } - - false + entry + .file_name() + .to_str() + .is_some_and(is_download_protected_root_name) } fn canonical_protocol_path(path: &Path) -> eyre::Result { @@ -659,7 +630,7 @@ pub async fn scan_local_library( let Some(game_id) = path.file_name().and_then(|n| n.to_str()) else { continue; }; - if is_ignored_game_root_name(game_id) { + if is_ignored_games_root_name(game_id) { continue; } diff --git a/crates/lanspread-peer/src/migration.rs b/crates/lanspread-peer/src/migration.rs index a23b8c4..019a5a3 100644 --- a/crates/lanspread-peer/src/migration.rs +++ b/crates/lanspread-peer/src/migration.rs @@ -8,20 +8,19 @@ use futures::{StreamExt as _, stream}; use tokio::io::AsyncWriteExt as _; use crate::{ - install::intent::{ - InstallIntent, + game_paths::{ + LEGACY_FIRST_START_DONE_FILE, LEGACY_INTENT_FILE, LEGACY_INTENT_TMP_FILE, - intent_path, - write_intent, + LEGACY_LIBRARY_INDEX_DIR, + LEGACY_SOFTLAN_INSTALL_MARKER, + is_ignored_games_root_name, }, - local_games::{is_ignored_game_root_name, legacy_library_index_path}, + install::intent::{InstallIntent, intent_path, write_intent}, + local_games::legacy_library_index_path, state_paths::{local_library_index_path, setup_done_path}, }; -const LEGACY_LIBRARY_INDEX_DIR: &str = ".lanspread"; -const LEGACY_FIRST_START_DONE_FILE: &str = ".softlan_first_start_done"; -const LEGACY_SOFTLAN_INSTALL_MARKER: &str = ".softlan_game_installed"; const MIGRATION_CONCURRENCY: usize = 16; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize)] @@ -97,7 +96,7 @@ async fn collect_game_roots(game_dir: &Path) -> std::io::Result eyre::Result> { let Some(name) = entry.file_name().to_str().map(ToOwned::to_owned) else { continue; }; - if is_ignored_game_root_name(&name) { + if is_ignored_games_root_name(&name) { continue; } roots.push(entry.path()); @@ -303,7 +299,7 @@ fn game_id_from_event_path(game_dir: &Path, path: &Path) -> Option { let relative = path.strip_prefix(game_dir).ok()?; let mut components = relative.components(); let game_id = component_name(components.next()?)?; - if is_ignored_game_root_name(game_id) { + if is_ignored_games_root_name(game_id) { return None; } @@ -324,13 +320,7 @@ fn component_name(component: Component<'_>) -> Option<&str> { } fn should_ignore_game_child(name: &str) -> bool { - is_local_dir_name(name) - || name.starts_with(".local.") - || name.starts_with(".version.ini.") - || name == ".lanspread" - || name == ".lanspread.json" - || name == ".sync" - || name == ".softlan_game_installed" + is_download_protected_root_name(name) } #[cfg(test)] diff --git a/crates/lanspread-peer/src/services/stream.rs b/crates/lanspread-peer/src/services/stream.rs index 240cf4f..5255f10 100644 --- a/crates/lanspread-peer/src/services/stream.rs +++ b/crates/lanspread-peer/src/services/stream.rs @@ -12,7 +12,8 @@ use crate::{ context::PeerCtx, error::PeerError, events, - local_games::{get_game_file_descriptions, is_local_dir_name, local_download_matches_catalog}, + game_paths::is_local_dir_name, + local_games::{get_game_file_descriptions, local_download_matches_catalog}, peer::{send_game_file_chunk, send_game_file_data}, services::handshake::{HandshakeCtx, accept_inbound_hello, spawn_library_resync}, stream_install::{send_game_install_stream, send_stream_install_error},