Files
Jackify/jackify/frontends/cli/commands/vnv_manual_downloads.py
2026-03-13 14:43:25 +00:00

141 lines
5.0 KiB
Python

"""CLI helpers for VNV manual-download handling."""
from pathlib import Path
from typing import Callable, Optional
from jackify.backend.services.nexus_premium_service import NexusPremiumService
from jackify.backend.services.vnv_post_install_service import VNVPostInstallService
from jackify.frontends.cli.commands.manual_download_flow import run_cli_manual_download_phase
from jackify.frontends.cli.ui.indeterminate_status import CliIndeterminateStatus
def _is_explicitly_non_premium(service: VNVPostInstallService) -> bool:
auth_token = service.auth_service.get_auth_token()
auth_method = service.auth_service.get_auth_method()
if not auth_token or not auth_method:
return False
is_premium, username = NexusPremiumService().check_premium_status(
auth_token,
is_oauth=auth_method == "oauth",
)
return username is not None and not is_premium
def _missing_manual_items(service: VNVPostInstallService) -> list[dict]:
completed = service.check_already_completed()
include_bsa = not completed["bsa_decompressed"] and not (
service._find_cached_bsa_mpi() or service._find_cached_bsa_package()
)
include_4gb = not completed["4gb_patch"] and not service._find_cached_4gb_patcher()
if not include_4gb and not include_bsa:
return []
items = service.get_manual_download_items(include_bsa=include_bsa)
if include_4gb:
return items
return [item for item in items if int(item.get("mod_id", 0)) != service.LINUX_4GB_PATCHER_MOD_ID]
def ensure_vnv_cli_manual_downloads(
service: VNVPostInstallService,
output_callback: Optional[Callable[[str], None]] = None,
) -> bool:
if not _is_explicitly_non_premium(service):
return True
items = _missing_manual_items(service)
if not items:
return True
output = output_callback or print
output("")
output("VNV requires manual Nexus downloads for this account. Opening Jackify CLI Download Manager...")
return run_cli_manual_download_phase(
events=items,
loop_iteration=1,
download_dir=service.cache_dir,
stdin_write=lambda _payload: True,
output_callback=output,
concurrent_limit=2,
)
def build_vnv_cli_manual_file_callback(
service: VNVPostInstallService,
output_callback: Optional[Callable[[str], None]] = None,
):
output = output_callback or print
manual_items = service.get_manual_download_items(include_bsa=True)
def _cached_file_for_title(title: str) -> Optional[Path]:
if "4GB" in title:
return service._find_cached_4gb_patcher()
return service._find_cached_bsa_mpi() or service._find_cached_bsa_package()
def _manual_file_callback(title: str, instructions: str) -> Optional[Path]:
cached = _cached_file_for_title(title)
if cached:
return cached
mod_id = (
service.LINUX_4GB_PATCHER_MOD_ID
if "4GB" in title
else service.FNV_BSA_DECOMPRESSOR_MOD_ID
)
item = next((entry for entry in manual_items if int(entry.get("mod_id", 0)) == mod_id), None)
if not item:
output("")
output(instructions)
return None
output("")
output(f"{title} - opening Jackify CLI Download Manager...")
success = run_cli_manual_download_phase(
events=[item],
loop_iteration=1,
download_dir=service.cache_dir,
stdin_write=lambda _payload: True,
output_callback=output,
concurrent_limit=1,
)
if not success:
return None
return _cached_file_for_title(title)
return _manual_file_callback
def create_vnv_cli_progress_callback(
output_callback: Optional[Callable[[str], None]] = None,
) -> tuple[Callable[[str], None], Callable[[], None]]:
"""Create a CLI progress callback with a pulser for indeterminate VNV stages."""
output = output_callback or print
pulser = CliIndeterminateStatus()
def _should_pulse(message: str) -> bool:
lowered = message.lower()
if "%" in lowered:
return False
if "assets processed:" in lowered:
return False
if "decompressing bsa files:" in lowered:
return False
pulse_markers = (
"running vnv post-install automation",
"running bsa decompressor",
"running 4gb patcher",
"preparing bsa decompressor package",
"extracting bsa package",
"ensuring ttw_linux_installer is available",
"checking for post-install automation",
"finalizing post-install configuration",
)
return any(marker in lowered for marker in pulse_markers)
def _progress(message: str) -> None:
text = (message or "").strip()
if not text:
return
if _should_pulse(text):
pulser.set(text)
return
pulser.stop()
output(text)
return _progress, pulser.close