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:
13 files changed
+198
-129
No files matched your search
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user