mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-08 03:47:44 +02:00
Sync from development - prepare for v0.3.0
This commit is contained in:
322
jackify/frontends/gui/screens/install_ttw_integration.py
Normal file
322
jackify/frontends/gui/screens/install_ttw_integration.py
Normal file
@@ -0,0 +1,322 @@
|
||||
"""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
|
||||
|
||||
Reference in New Issue
Block a user