mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-08-14 02:13:43 +02:00
Release v0.7 - Tools Hub, Engine Choice, NXM Link Handling, Native Component Install, NSF/CSF Support
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Engine Invoker
|
||||
|
||||
Resolves the active install engine and builds the appropriate subprocess command.
|
||||
Keeps engine-specific CLI differences isolated from install workflow code.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_active_engine_id() -> str:
|
||||
from jackify.backend.services.tool_registry import get_active_engine_id as _get
|
||||
return _get()
|
||||
|
||||
|
||||
def is_clf3_active() -> bool:
|
||||
return get_active_engine_id() == "clf3"
|
||||
|
||||
|
||||
def ensure_engine_available(engine_id: str = "jackify-engine") -> Tuple[bool, str]:
|
||||
"""
|
||||
Check that the given engine binary is present and download it if not.
|
||||
Returns (True, path) on success, (False, error_message) on failure.
|
||||
Call this at startup before the first install attempt.
|
||||
"""
|
||||
from jackify.backend.services.tool_registry import ToolRegistry
|
||||
path = get_engine_path(engine_id)
|
||||
if path:
|
||||
return True, path
|
||||
logger.info("Engine %s not found, attempting download via Tools Hub", engine_id)
|
||||
ok, msg = ToolRegistry().install(engine_id)
|
||||
if not ok:
|
||||
return False, msg
|
||||
path = get_engine_path(engine_id)
|
||||
if not path:
|
||||
return False, f"{engine_id} downloaded but binary not found after install"
|
||||
return True, path
|
||||
|
||||
|
||||
def get_engine_path(engine_id: str) -> Optional[str]:
|
||||
"""Return the filesystem path to the engine binary for the given engine_id."""
|
||||
from jackify.backend.services.tool_registry import ToolRegistry
|
||||
path = ToolRegistry().get_binary_path(engine_id)
|
||||
if path and path.is_file():
|
||||
return str(path)
|
||||
|
||||
if engine_id == "jackify-engine":
|
||||
from jackify.backend.core.modlist_operations import get_jackify_engine_path
|
||||
return get_jackify_engine_path()
|
||||
|
||||
logger.warning("Engine binary not found for engine_id=%s", engine_id)
|
||||
return None
|
||||
|
||||
|
||||
def get_active_engine_path() -> Optional[str]:
|
||||
"""Return the binary path for the currently active engine."""
|
||||
return get_engine_path(get_active_engine_id())
|
||||
|
||||
|
||||
def resolve_game_dir(game_type: Optional[str], modlist_path: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
Resolve the vanilla game installation directory for CLF3's --game argument.
|
||||
Searches Steam and Heroic-managed stores in order.
|
||||
Returns None if the path cannot be determined.
|
||||
Use resolve_game_location() when the store identity is also needed.
|
||||
"""
|
||||
result = resolve_game_location(game_type)
|
||||
return result[0] if result else None
|
||||
|
||||
|
||||
def resolve_game_location(game_type: Optional[str]) -> Optional[tuple]:
|
||||
"""
|
||||
Return (path_str, store) for the detected game installation, or None.
|
||||
store is one of: 'steam', 'gog', 'epic', 'unknown'.
|
||||
"""
|
||||
if not game_type:
|
||||
return None
|
||||
try:
|
||||
from jackify.backend.handlers.vanilla_game_finder import VanillaGameFinder
|
||||
result = VanillaGameFinder().find(game_type)
|
||||
if result:
|
||||
path, store = result
|
||||
return str(path), store
|
||||
except Exception as e:
|
||||
logger.debug("Game dir detection failed for %s: %s", game_type, e)
|
||||
return None
|
||||
|
||||
|
||||
def build_install_command(
|
||||
engine_id: str,
|
||||
engine_path: str,
|
||||
wabbajack: str,
|
||||
install_dir: str,
|
||||
downloads_dir: str,
|
||||
game_dir: Optional[str] = None,
|
||||
install_mode: str = "online",
|
||||
debug: bool = False,
|
||||
) -> List[str]:
|
||||
"""Build the subprocess install command for the given engine."""
|
||||
if engine_id == "clf3":
|
||||
return _build_clf3_command(engine_path, wabbajack, install_dir, downloads_dir, game_dir)
|
||||
return _build_jackify_engine_command(engine_path, wabbajack, install_dir, downloads_dir, install_mode, debug, game_dir)
|
||||
|
||||
|
||||
def _build_jackify_engine_command(
|
||||
engine_path: str,
|
||||
wabbajack: str,
|
||||
install_dir: str,
|
||||
downloads_dir: str,
|
||||
install_mode: str,
|
||||
debug: bool,
|
||||
game_dir: Optional[str] = None,
|
||||
) -> List[str]:
|
||||
cmd = [engine_path, "install", "--show-file-progress"]
|
||||
if wabbajack.endswith(".wabbajack") and os.path.isfile(wabbajack):
|
||||
cmd += ["-w", wabbajack]
|
||||
else:
|
||||
cmd += ["-m", wabbajack]
|
||||
cmd += ["-o", install_dir, "-d", downloads_dir]
|
||||
if game_dir:
|
||||
cmd += ["-g", game_dir]
|
||||
if debug:
|
||||
cmd.append("--debug")
|
||||
return cmd
|
||||
|
||||
|
||||
def _read_resource_settings() -> dict:
|
||||
"""Read resource_settings.json from the Jackify config dir; return empty dict on any failure."""
|
||||
try:
|
||||
from jackify.shared.paths import get_jackify_config_dir
|
||||
import json
|
||||
path = get_jackify_config_dir() / "resource_settings.json"
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _clf3_default_workers() -> int:
|
||||
"""Default worker count matching jackify-engine: full cpu_count."""
|
||||
import multiprocessing
|
||||
return max(1, multiprocessing.cpu_count() or 4)
|
||||
|
||||
|
||||
def _build_clf3_command(
|
||||
engine_path: str,
|
||||
wabbajack: str,
|
||||
install_dir: str,
|
||||
downloads_dir: str,
|
||||
game_dir: Optional[str] = None,
|
||||
) -> List[str]:
|
||||
# Positional order: <WABBAJACK_FILE> <DOWNLOADS> <OUTPUT>
|
||||
# CLF3 performs its own game detection (Steam + Heroic) with file verification.
|
||||
# game_dir is reserved for explicit edge-case overrides only.
|
||||
cmd = [engine_path, "install", "--jackify"]
|
||||
if os.environ.get("JACKIFY_CLF3_VERBOSE"):
|
||||
cmd.append("--verbose")
|
||||
logger.info("CLF3 verbose mode enabled (JACKIFY_CLF3_VERBOSE)")
|
||||
if game_dir:
|
||||
cmd += ["--game", game_dir]
|
||||
|
||||
res = _read_resource_settings()
|
||||
default = _clf3_default_workers()
|
||||
|
||||
def _tasks(key: str) -> int:
|
||||
val = res.get(key, {}).get("MaxTasks", 0)
|
||||
return val if val > 0 else default
|
||||
|
||||
concurrent = _tasks("Downloads")
|
||||
install_workers = _tasks("Installer")
|
||||
sevenzip_workers = _tasks("File Extractor")
|
||||
|
||||
cmd += [
|
||||
"--concurrent", str(concurrent),
|
||||
"--install-workers", str(install_workers),
|
||||
"--sevenzip-workers", str(sevenzip_workers),
|
||||
]
|
||||
logger.debug(
|
||||
"CLF3 resource flags: concurrent=%d install_workers=%d sevenzip_workers=%d (from %s)",
|
||||
concurrent, install_workers, sevenzip_workers,
|
||||
"resource_settings.json" if res else "default (cpu_count)",
|
||||
)
|
||||
|
||||
cmd += [wabbajack, downloads_dir, install_dir]
|
||||
return cmd
|
||||
Reference in New Issue
Block a user