Files
Jackify/jackify/backend/services/update_detection.py
T

123 lines
4.2 KiB
Python

"""Update-vs-new installation detection service.
Free functions used by both GUI (install_modlist_workflow.py) and CLI
(modlist_operations.py) to avoid duplicated logic.
"""
import os
import logging
from typing import Optional
logger = logging.getLogger(__name__)
def normalize_version_token(value: Optional[str]) -> Optional[str]:
"""Return a normalised version token for equality checks."""
if value is None:
return None
token = str(value).strip()
if not token:
return None
return token.lstrip("vV").lower()
def normalize_modlist_name(value: Optional[str]) -> str:
"""Return a case/whitespace-normalised modlist name for comparison."""
return " ".join((value or "").strip().lower().split())
def evaluate_update_candidate(
modlist_name: str,
install_dir: str,
existing_appid: Optional[str],
requested_version: Optional[str] = None,
) -> tuple[bool, dict]:
"""Decide whether update-mode should be offered.
Args:
modlist_name: Name of the modlist being installed.
install_dir: Resolved installation directory path.
existing_appid: Steam AppID from an existing shortcut (None if not found).
requested_version: Pre-computed normalised version from the selected modlist
(pass None when unavailable, e.g. offline/file-based installs).
Returns:
(eligible, result_dict) where eligible is True when update mode is safe to offer.
"""
from jackify.backend.utils.modlist_meta import read_modlist_meta
result: dict = {
"eligible": False,
"reason": "unknown",
"requested_version": None,
"installed_version": None,
"version_relation": "unknown",
"installed_name": None,
}
if not existing_appid:
result["reason"] = "missing_shortcut_appid"
return False, result
meta = read_modlist_meta(install_dir)
if not meta:
result["reason"] = "missing_meta"
return False, result
installed_name = (meta.get("modlist_name") or "").strip()
result["installed_name"] = installed_name
if normalize_modlist_name(installed_name) != normalize_modlist_name(modlist_name):
result["reason"] = "modlist_name_mismatch"
return False, result
installed_version = normalize_version_token(meta.get("modlist_version"))
result["requested_version"] = requested_version
result["installed_version"] = installed_version
if requested_version and installed_version:
result["version_relation"] = (
"same" if requested_version == installed_version else "different"
)
result["eligible"] = True
result["reason"] = "eligible"
return True, result
def find_existing_shortcut_appid(modlist_name: str, install_dir: str) -> Optional[str]:
"""Return the Steam AppID of an existing shortcut for this install, or None."""
try:
from jackify.backend.handlers.shortcut_handler import ShortcutHandler
from jackify.backend.services.platform_detection_service import PlatformDetectionService
platform_service = PlatformDetectionService.get_instance()
shortcut_handler = ShortcutHandler(
steamdeck=platform_service.is_steamdeck, verbose=False
)
install_real = os.path.realpath(install_dir)
candidate_exes = [
os.path.join(install_real, "ModOrganizer.exe"),
os.path.join(install_real, "files", "ModOrganizer.exe"),
]
for exe_path in candidate_exes:
if not os.path.exists(exe_path):
continue
appid = shortcut_handler.get_appid_from_vdf(modlist_name, exe_path)
if appid:
return appid
for shortcut in shortcut_handler.find_shortcuts_by_exe("ModOrganizer.exe"):
if (
(shortcut.get("AppName", "").strip() == modlist_name.strip())
and os.path.realpath(shortcut.get("StartDir", "")) == install_real
):
raw_appid = shortcut.get("appid")
if raw_appid is not None:
return str(int(raw_appid) & 0xFFFFFFFF)
except Exception as e:
logger.warning("Update detection: failed shortcut lookup: %s", e)
return None