mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-08 00:17:58 +02:00
Sync from development - prepare for v0.5.0.2
This commit is contained in:
@@ -34,7 +34,8 @@ class InstallerThread(QThread):
|
||||
non_premium_detected = Signal()
|
||||
|
||||
def __init__(self, modlist, install_dir, downloads_dir, api_key, modlist_name,
|
||||
install_mode='online', progress_state_manager=None, auth_service=None, oauth_info=None):
|
||||
install_mode='online', progress_state_manager=None, auth_service=None,
|
||||
oauth_info=None, skip_disk_check=False):
|
||||
super().__init__()
|
||||
self.modlist = modlist
|
||||
self.install_dir = install_dir
|
||||
@@ -47,6 +48,7 @@ class InstallerThread(QThread):
|
||||
self.progress_state_manager = progress_state_manager
|
||||
self.auth_service = auth_service
|
||||
self.oauth_info = oauth_info
|
||||
self.skip_disk_check = skip_disk_check
|
||||
self._premium_signal_sent = False
|
||||
self._non_premium_info_sent = False
|
||||
self._engine_output_buffer = []
|
||||
@@ -56,6 +58,8 @@ class InstallerThread(QThread):
|
||||
self._raw_stdout_lines: list = [] # bounded ring buffer for non-JSON stdout
|
||||
self._pending_manual_downloads: list = [] # accumulates items until list_complete
|
||||
self._resource_limit_hint: Optional[str] = None
|
||||
self._install_progress_started = False # True once any [FILE_PROGRESS] output seen
|
||||
self._last_error_raw_context: dict = {} # raw context dict from structured engine errors
|
||||
|
||||
@staticmethod
|
||||
def _is_generic_failure_text(message: Optional[str]) -> bool:
|
||||
@@ -136,6 +140,12 @@ class InstallerThread(QThread):
|
||||
error = parse_engine_error_line(line)
|
||||
if error and self.last_error is None:
|
||||
self.last_error = error
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
if obj.get("type") == "disk_full":
|
||||
self._last_error_raw_context = obj.get("context") or {}
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
else:
|
||||
if self.last_error is None and is_cc_content_error(line):
|
||||
self.last_error = cc_content_missing(extract_cc_filename(line) or "")
|
||||
@@ -265,6 +275,9 @@ class InstallerThread(QThread):
|
||||
if debug_mode:
|
||||
cmd.append('--debug')
|
||||
logger.debug("DEBUG: Added --debug flag to jackify-engine command")
|
||||
if self.skip_disk_check:
|
||||
cmd.append('--skip-disk-check')
|
||||
logger.debug("DEBUG: Added --skip-disk-check flag to jackify-engine command")
|
||||
logger.debug(f"DEBUG: FULL Engine command: {' '.join(cmd)}")
|
||||
logger.debug(f"DEBUG: modlist value being passed: '{self.modlist}'")
|
||||
from jackify.backend.handlers.subprocess_utils import get_clean_subprocess_env
|
||||
@@ -361,6 +374,7 @@ class InstallerThread(QThread):
|
||||
logger.debug(f"DEBUG: Parser detected {len(progress_state.active_files)} active files from line: {decoded[:80]}")
|
||||
self.progress_updated.emit(progress_state)
|
||||
if '[FILE_PROGRESS]' in decoded:
|
||||
self._install_progress_started = True
|
||||
parts = decoded.split('[FILE_PROGRESS]', 1)
|
||||
if parts[0].strip():
|
||||
self.progress_received.emit(parts[0].rstrip())
|
||||
@@ -427,6 +441,7 @@ class InstallerThread(QThread):
|
||||
continue
|
||||
self._remember_stdout_line(decoded)
|
||||
if '[FILE_PROGRESS]' in decoded:
|
||||
self._install_progress_started = True
|
||||
parts = decoded.split('[FILE_PROGRESS]', 1)
|
||||
if parts[0].strip():
|
||||
self.output_received.emit(parts[0].rstrip())
|
||||
|
||||
@@ -406,6 +406,18 @@ class ProgressHandlersMixin:
|
||||
if self._premium_failure_active:
|
||||
message = "Installation stopped because Nexus Premium is required for automated downloads."
|
||||
|
||||
if not self._premium_failure_active and not cancellation_detected:
|
||||
thread = getattr(self, 'install_thread', None)
|
||||
if (thread
|
||||
and not getattr(thread, '_install_progress_started', False)
|
||||
and getattr(getattr(thread, 'last_error', None), 'title', '') == "Disk Full"):
|
||||
ctx = getattr(thread, '_last_error_raw_context', {})
|
||||
if self._handle_preflight_disk_space(ctx):
|
||||
return
|
||||
self._installation_cancelled = True
|
||||
self.process_finished(130, QProcess.NormalExit)
|
||||
return
|
||||
|
||||
if not self._premium_failure_active:
|
||||
engine_error = getattr(self.install_thread, 'last_error', None)
|
||||
if engine_error:
|
||||
@@ -417,6 +429,68 @@ class ProgressHandlersMixin:
|
||||
self._safe_append_text(f"\nError: {message}")
|
||||
self.process_finished(1, QProcess.CrashExit) # Simulate error
|
||||
|
||||
def _handle_preflight_disk_space(self, ctx: dict) -> bool:
|
||||
"""Show pre-flight disk space warning dialog. Returns True if user chose Continue Anyway."""
|
||||
required_bytes = ctx.get('required_bytes', 0)
|
||||
available_bytes = ctx.get('available_bytes', 0)
|
||||
|
||||
def _fmt(b):
|
||||
if b >= 1024 ** 3:
|
||||
return f"{b / 1024 ** 3:.1f} GB"
|
||||
if b >= 1024 ** 2:
|
||||
return f"{b / 1024 ** 2:.1f} MB"
|
||||
return f"{b} bytes" if b else "unknown"
|
||||
|
||||
required_str = _fmt(required_bytes)
|
||||
available_str = _fmt(available_bytes)
|
||||
|
||||
body = (
|
||||
f"The disk space check reports that there may not be enough free space to complete "
|
||||
f"this installation.\n\n"
|
||||
f"Required: {required_str}\n"
|
||||
f"Available: {available_str}\n\n"
|
||||
f"If this is a modlist update, the actual space needed is likely far less — most files "
|
||||
f"are already present and will be reused rather than re-downloaded.\n\n"
|
||||
f"You can continue and free up space while downloads are running, "
|
||||
f"or cancel to resolve the space issue first."
|
||||
)
|
||||
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
dlg = QMessageBox(self)
|
||||
dlg.setWindowTitle("Disk Space Warning")
|
||||
dlg.setText("Not enough free disk space detected.")
|
||||
dlg.setInformativeText(body)
|
||||
dlg.setIcon(QMessageBox.Warning)
|
||||
continue_btn = dlg.addButton("Continue Anyway", QMessageBox.AcceptRole)
|
||||
dlg.addButton("Cancel", QMessageBox.RejectRole)
|
||||
dlg.setDefaultButton(continue_btn)
|
||||
dlg.exec()
|
||||
|
||||
if dlg.clickedButton() is not continue_btn:
|
||||
return False
|
||||
|
||||
thread = getattr(self, 'install_thread', None)
|
||||
if not thread:
|
||||
return False
|
||||
|
||||
modlist = getattr(thread, 'modlist', None)
|
||||
install_dir = getattr(thread, 'install_dir', None)
|
||||
downloads_dir = getattr(thread, 'downloads_dir', None)
|
||||
api_key = getattr(thread, 'api_key', None)
|
||||
install_mode = getattr(thread, 'install_mode', 'online')
|
||||
oauth_info = getattr(thread, 'oauth_info', None)
|
||||
|
||||
if not (modlist and install_dir and downloads_dir and api_key):
|
||||
return False
|
||||
|
||||
logger.info("Pre-flight disk space check bypassed by user — restarting with --skip-disk-check")
|
||||
self._safe_append_text("\n[WARN] Disk space check bypassed. Continuing installation...\n")
|
||||
self.run_modlist_installer(
|
||||
modlist, install_dir, downloads_dir, api_key,
|
||||
install_mode, oauth_info, skip_disk_check=True,
|
||||
)
|
||||
return True
|
||||
|
||||
def process_finished(self, exit_code, exit_status):
|
||||
logger.debug(f"DEBUG: process_finished called with exit_code={exit_code}, exit_status={exit_status}")
|
||||
# Reset button states
|
||||
|
||||
@@ -384,7 +384,7 @@ class InstallWorkflowExecutionMixin:
|
||||
self.cancel_install_btn.setVisible(False)
|
||||
logger.debug(f"DEBUG: Controls re-enabled in exception handler")
|
||||
|
||||
def run_modlist_installer(self, modlist, install_dir, downloads_dir, api_key, install_mode='online', oauth_info=None):
|
||||
def run_modlist_installer(self, modlist, install_dir, downloads_dir, api_key, install_mode='online', oauth_info=None, skip_disk_check=False):
|
||||
logger.debug('DEBUG: run_modlist_installer called - USING THREADED BACKEND WRAPPER')
|
||||
|
||||
# Rotate log file at start of each workflow run (keep 5 backups)
|
||||
@@ -408,7 +408,8 @@ class InstallWorkflowExecutionMixin:
|
||||
modlist, install_dir, downloads_dir, api_key, self.modlist_name_edit.text().strip(), install_mode,
|
||||
progress_state_manager=self.progress_state_manager, # R&D: Pass progress state manager
|
||||
auth_service=self.auth_service, # Fix Issue #127: Pass auth_service for Premium detection diagnostics
|
||||
oauth_info=oauth_info # Pass OAuth state for auto-refresh
|
||||
oauth_info=oauth_info, # Pass OAuth state for auto-refresh
|
||||
skip_disk_check=skip_disk_check,
|
||||
)
|
||||
self.install_thread.output_received.connect(self.on_installation_output)
|
||||
self.install_thread.progress_received.connect(self.on_installation_progress)
|
||||
|
||||
Reference in New Issue
Block a user