mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-08 01:47:45 +02:00
323 lines
14 KiB
Python
323 lines
14 KiB
Python
"""Modlist integration workflow for InstallTTWScreen (Mixin)."""
|
|
from PySide6.QtCore import QThread, Signal, Qt
|
|
from PySide6.QtWidgets import QProgressDialog, QApplication
|
|
from jackify.frontends.gui.services.message_service import MessageService
|
|
from pathlib import Path
|
|
import traceback
|
|
import os
|
|
import json
|
|
import shutil
|
|
import re
|
|
|
|
|
|
def debug_print(message):
|
|
"""Print debug message only if debug mode is enabled"""
|
|
from jackify.backend.handlers.config_handler import ConfigHandler
|
|
config_handler = ConfigHandler()
|
|
if config_handler.get('debug_mode', False):
|
|
print(message)
|
|
|
|
|
|
class TTWIntegrationMixin:
|
|
"""Mixin providing modlist integration workflow for InstallTTWScreen."""
|
|
|
|
def set_modlist_integration_mode(self, modlist_name: str, install_dir: str):
|
|
"""Set the screen to modlist integration mode
|
|
|
|
This mode is activated when TTW needs to be installed and integrated
|
|
into an existing modlist. In this mode, after TTW installation completes,
|
|
the TTW output will be automatically integrated into the modlist.
|
|
|
|
Args:
|
|
modlist_name: Name of the modlist to integrate TTW into
|
|
install_dir: Installation directory of the modlist
|
|
"""
|
|
self._integration_mode = True
|
|
self._integration_modlist_name = modlist_name
|
|
self._integration_install_dir = install_dir
|
|
|
|
# Reset saved geometry so showEvent can properly collapse from current window size
|
|
self._saved_geometry = None
|
|
self._saved_min_size = None
|
|
|
|
# Update UI to show integration mode
|
|
debug_print(f"TTW screen set to integration mode for modlist: {modlist_name}")
|
|
debug_print(f"Installation directory: {install_dir}")
|
|
|
|
def _perform_modlist_integration(self):
|
|
"""Integrate TTW into the modlist automatically
|
|
|
|
This is called when in integration mode. It will:
|
|
1. Copy TTW output to modlist's mods folder
|
|
2. Update modlist.txt for all profiles
|
|
3. Update plugins.txt with TTW ESMs in correct order
|
|
4. Emit integration_complete signal
|
|
"""
|
|
try:
|
|
from pathlib import Path
|
|
import re
|
|
from PySide6.QtCore import QThread, Signal
|
|
|
|
# Get TTW output directory
|
|
ttw_output_dir = Path(self.install_dir_edit.text())
|
|
if not ttw_output_dir.exists():
|
|
error_msg = f"TTW output directory not found: {ttw_output_dir}"
|
|
self._safe_append_text(f"\nError: {error_msg}")
|
|
self.integration_complete.emit(False, "")
|
|
return
|
|
|
|
# Extract version from .mpi filename
|
|
mpi_path = self.file_edit.text().strip()
|
|
ttw_version = ""
|
|
if mpi_path:
|
|
mpi_filename = Path(mpi_path).stem
|
|
version_match = re.search(r'v?(\d+\.\d+(?:\.\d+)?)', mpi_filename, re.IGNORECASE)
|
|
if version_match:
|
|
ttw_version = version_match.group(1)
|
|
|
|
# Create background thread for integration
|
|
class IntegrationThread(QThread):
|
|
finished = Signal(bool, str) # success, ttw_version
|
|
progress = Signal(str) # progress message
|
|
|
|
def __init__(self, ttw_output_path, modlist_install_dir, ttw_version):
|
|
super().__init__()
|
|
self.ttw_output_path = ttw_output_path
|
|
self.modlist_install_dir = modlist_install_dir
|
|
self.ttw_version = ttw_version
|
|
|
|
def run(self):
|
|
try:
|
|
from jackify.backend.handlers.ttw_installer_handler import TTWInstallerHandler
|
|
|
|
self.progress.emit("Integrating TTW into modlist...")
|
|
success = TTWInstallerHandler.integrate_ttw_into_modlist(
|
|
ttw_output_path=self.ttw_output_path,
|
|
modlist_install_dir=self.modlist_install_dir,
|
|
ttw_version=self.ttw_version
|
|
)
|
|
self.finished.emit(success, self.ttw_version)
|
|
except Exception as e:
|
|
debug_print(f"ERROR: Integration thread failed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
self.finished.emit(False, self.ttw_version)
|
|
|
|
# Show progress message
|
|
self._safe_append_text("\nIntegrating TTW into modlist (this may take a few minutes)...")
|
|
|
|
# Update status banner (only in integration mode - visible when collapsed)
|
|
if self._integration_mode:
|
|
self.status_banner.setText("Integrating TTW into modlist (this may take a few minutes)...")
|
|
self.status_banner.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: #FFA500;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
border-radius: 5px;
|
|
}}
|
|
""")
|
|
|
|
# Create progress dialog for integration
|
|
progress_dialog = QProgressDialog(
|
|
f"Integrating TTW {ttw_version} into modlist...\n\n"
|
|
"This involves copying several GB of files and may take a few minutes.\n"
|
|
"Please wait...",
|
|
None, # No cancel button
|
|
0, 0, # Indeterminate progress
|
|
self
|
|
)
|
|
progress_dialog.setWindowTitle("Integrating TTW")
|
|
progress_dialog.setMinimumDuration(0) # Show immediately
|
|
progress_dialog.setWindowModality(Qt.ApplicationModal)
|
|
progress_dialog.setCancelButton(None)
|
|
progress_dialog.show()
|
|
QApplication.processEvents()
|
|
|
|
# Store reference to close later
|
|
self._integration_progress_dialog = progress_dialog
|
|
|
|
# Create and start integration thread
|
|
self.integration_thread = IntegrationThread(
|
|
ttw_output_dir,
|
|
Path(self._integration_install_dir),
|
|
ttw_version
|
|
)
|
|
self.integration_thread.progress.connect(self._safe_append_text)
|
|
self.integration_thread.finished.connect(self._on_integration_thread_finished)
|
|
self.integration_thread.start()
|
|
|
|
except Exception as e:
|
|
# Close progress dialog if it exists
|
|
if hasattr(self, '_integration_progress_dialog'):
|
|
self._integration_progress_dialog.close()
|
|
delattr(self, '_integration_progress_dialog')
|
|
|
|
error_msg = f"Integration error: {str(e)}"
|
|
self._safe_append_text(f"\nError: {error_msg}")
|
|
debug_print(f"ERROR: {error_msg}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
self.integration_complete.emit(False, "")
|
|
|
|
def _on_integration_thread_finished(self, success: bool, ttw_version: str):
|
|
"""Handle completion of integration thread"""
|
|
try:
|
|
# Close progress dialog
|
|
if hasattr(self, '_integration_progress_dialog'):
|
|
self._integration_progress_dialog.close()
|
|
delattr(self, '_integration_progress_dialog')
|
|
|
|
if success:
|
|
self._safe_append_text("\nTTW integration completed successfully!")
|
|
|
|
# Update status banner (only in integration mode)
|
|
if self._integration_mode:
|
|
self.status_banner.setText("TTW integration completed successfully!")
|
|
self.status_banner.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: #28a745;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
border-radius: 5px;
|
|
}}
|
|
""")
|
|
|
|
MessageService.information(
|
|
self, "Integration Complete",
|
|
f"TTW {ttw_version} has been successfully integrated into {self._integration_modlist_name}!",
|
|
safety_level="medium"
|
|
)
|
|
self.integration_complete.emit(True, ttw_version)
|
|
else:
|
|
self._safe_append_text("\nTTW integration failed!")
|
|
|
|
# Update status banner (only in integration mode)
|
|
if self._integration_mode:
|
|
self.status_banner.setText("TTW integration failed!")
|
|
self.status_banner.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: #dc3545;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
border-radius: 5px;
|
|
}}
|
|
""")
|
|
|
|
MessageService.critical(
|
|
self, "Integration Failed",
|
|
"Failed to integrate TTW into the modlist. Check the log for details."
|
|
)
|
|
self.integration_complete.emit(False, ttw_version)
|
|
except Exception as e:
|
|
debug_print(f"ERROR: Failed to handle integration completion: {e}")
|
|
self.integration_complete.emit(False, ttw_version)
|
|
|
|
def _create_ttw_mod_archive(self, automated=False):
|
|
"""Create a zipped mod archive of TTW output for MO2 installation.
|
|
|
|
Args:
|
|
automated: If True, runs silently without user prompts (for automation)
|
|
"""
|
|
try:
|
|
from pathlib import Path
|
|
import re
|
|
from PySide6.QtCore import QThread, Signal
|
|
|
|
output_dir = Path(self.install_dir_edit.text())
|
|
if not output_dir.exists():
|
|
if not automated:
|
|
MessageService.warning(self, "Output Directory Not Found",
|
|
f"Output directory does not exist:\n{output_dir}")
|
|
return False
|
|
|
|
# Extract version from .mpi filename (e.g., "TTW v3.4.mpi" -> "3.4")
|
|
mpi_path = self.file_edit.text().strip()
|
|
version_suffix = ""
|
|
if mpi_path:
|
|
mpi_filename = Path(mpi_path).stem
|
|
version_match = re.search(r'v?(\d+\.\d+(?:\.\d+)?)', mpi_filename, re.IGNORECASE)
|
|
if version_match:
|
|
version_suffix = f" {version_match.group(1)}"
|
|
|
|
# Create archive filename
|
|
archive_name = f"[NoDelete] Tale of Two Wastelands{version_suffix}"
|
|
archive_path = output_dir.parent / archive_name
|
|
|
|
# Create background thread for zip creation
|
|
class ZipCreationThread(QThread):
|
|
finished = Signal(bool, str) # success, result_message
|
|
|
|
def __init__(self, output_dir, archive_path):
|
|
super().__init__()
|
|
self.output_dir = output_dir
|
|
self.archive_path = archive_path
|
|
|
|
def run(self):
|
|
try:
|
|
import shutil
|
|
final_archive = shutil.make_archive(
|
|
str(self.archive_path),
|
|
'zip',
|
|
str(self.output_dir)
|
|
)
|
|
self.finished.emit(True, str(final_archive))
|
|
except Exception as e:
|
|
self.finished.emit(False, str(e))
|
|
|
|
# Create progress dialog (non-modal so UI stays responsive)
|
|
progress_dialog = QProgressDialog(
|
|
f"Creating mod archive: {archive_name}.zip\n\n"
|
|
"This may take several minutes depending on installation size...",
|
|
"Cancel",
|
|
0, 0, # 0,0 = indeterminate progress bar
|
|
self
|
|
)
|
|
progress_dialog.setWindowTitle("Creating Archive")
|
|
progress_dialog.setMinimumDuration(0) # Show immediately
|
|
progress_dialog.setWindowModality(Qt.ApplicationModal)
|
|
progress_dialog.setCancelButton(None) # Cannot cancel zip operation safely
|
|
progress_dialog.show()
|
|
QApplication.processEvents()
|
|
|
|
# Create and start thread
|
|
zip_thread = ZipCreationThread(output_dir, archive_path)
|
|
|
|
def on_zip_finished(success, result):
|
|
progress_dialog.close()
|
|
if success:
|
|
final_archive = result
|
|
if not automated:
|
|
self._safe_append_text(f"\nArchive created successfully: {Path(final_archive).name}")
|
|
MessageService.information(
|
|
self, "Archive Created",
|
|
f"TTW mod archive created successfully!\n\n"
|
|
f"Location: {final_archive}\n\n"
|
|
f"You can now install this archive as a mod in MO2.",
|
|
safety_level="medium"
|
|
)
|
|
else:
|
|
error_msg = f"Failed to create mod archive: {result}"
|
|
if not automated:
|
|
self._safe_append_text(f"\nError: {error_msg}")
|
|
MessageService.critical(self, "Archive Creation Failed", error_msg)
|
|
|
|
zip_thread.finished.connect(on_zip_finished)
|
|
zip_thread.start()
|
|
|
|
# Keep reference to prevent garbage collection
|
|
self._zip_thread = zip_thread
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
error_msg = f"Failed to create mod archive: {str(e)}"
|
|
if not automated:
|
|
self._safe_append_text(f"\nError: {error_msg}")
|
|
MessageService.critical(self, "Archive Creation Failed", error_msg)
|
|
return False
|
|
|