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
This commit is contained in:
2026-08-09 17:59:01 +02:00
parent a6ed60a538
commit a1013b028d
13 changed files with 198 additions and 129 deletions
+8 -9
View File
@@ -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<Vec<(String, Pat
let Some(id) = entry.file_name().to_str().map(ToOwned::to_owned) else {
continue;
};
if is_ignored_game_root_name(&id) {
if is_ignored_games_root_name(&id) {
continue;
}