diff --git a/drums.gif b/drums.gif deleted file mode 100644 index f77ee34..0000000 Binary files a/drums.gif and /dev/null differ diff --git a/loaddaemon.py b/loaddaemon.py index 6653eed..8ddcac1 100644 --- a/loaddaemon.py +++ b/loaddaemon.py @@ -3,6 +3,7 @@ import threading import time import shlex import gc +import re from io import StringIO from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QProgressBar, QPushButton, QHBoxLayout from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer, QEvent @@ -40,7 +41,7 @@ class StreamingCapture(StringIO): # Signal/Object might be deleted pass - if not self.launch_detected: + if not self.launch_detected and self.launch_signal: lower_text = text.lower() if "launching" in lower_text and ("game" in lower_text or "version" in lower_text or "minecraft" in lower_text): self.launch_detected = True @@ -193,8 +194,174 @@ class LaunchWindow(QDialog): pass +class PrepareWindow(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Preparing Version") + self.setModal(True) + self.resize(400, 150) + + layout = QVBoxLayout() + + self.status_label = QLabel("Initializing...") + self.status_label.setWordWrap(True) + self.status_label.setAlignment(Qt.AlignCenter) + layout.addWidget(self.status_label) + + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 0) # Indeterminate progress + self.progress_bar.setTextVisible(False) + layout.addWidget(self.progress_bar) + + # Add a manual close/cancel button + button_layout = QHBoxLayout() + button_layout.addStretch() + self.cancel_button = QPushButton("Cancel") + self.cancel_button.clicked.connect(self.request_abort) + button_layout.addWidget(self.cancel_button) + layout.addLayout(button_layout) + + self.setLayout(layout) + + self.signals = LaunchSignals() + self.signals.log_update.connect(self.update_status) + self.signals.launch_complete.connect(self.on_prepare_complete) + self.signals.launch_aborted.connect(self.on_prepare_aborted) + self.signals.cleanup_done.connect(self.on_cleanup_done) + + self.aborting = False + self.capture_streams = [] + self.thread_running = False + self.success = False + + def update_status(self, text): + # Parse output for progress updates + + if "Downloading" in text and "libraries" in text: + try: + count = int(re.search(r'\d+', text).group()) + self.status_label.setText(f"Downloading {count} libraries...") + except: + self.status_label.setText(text) + elif "Checking" in text and "assets" in text: + try: + count = int(re.search(r'\d+', text).group()) + self.status_label.setText(f"Checking {count} assets...") + except: + self.status_label.setText(text) + elif "Jar file" in text and "downloaded" in text: + self.status_label.setText("Downloading game jar...") + elif "Checking libraries" in text: + self.status_label.setText("Checking libraries...") + else: + if len(text) > 100: + text = text[:97] + "..." + self.status_label.setText(text) + + def on_prepare_complete(self): + if not self.aborting: + self.success = True + self.status_label.setText("Version prepared successfully!") + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(100) + self.cancel_button.setEnabled(False) + QTimer.singleShot(1500, self.accept) + + def on_prepare_aborted(self): + self.status_label.setText("Preparation Aborted.") + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.success = False + + def on_cleanup_done(self): + self.thread_running = False + if not self.success: + super().reject() + + def request_abort(self): + if self.thread_running and not self.aborting: + self.aborting = True + self.status_label.setText("Aborting...") + self.cancel_button.setEnabled(False) + # Signal streams to stop + for stream in self.capture_streams: + stream.abort_requested = True + elif not self.thread_running: + super().reject() + + def reject(self): + self.request_abort() + + def closeEvent(self, event): + if self.thread_running: + event.ignore() + self.request_abort() + else: + event.accept() + + def prepare_version(self, version): + command = f"version prepare {version}" + self.thread_running = True + thread = threading.Thread(target=self._run_prepare, args=(command,), daemon=True) + thread.start() + + def _run_prepare(self, command): + try: + modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')] + for mod in modules_to_remove: + del sys.modules[mod] + gc.collect() + + from zucaro.cli.main import zucaro_cli + + old_stdout, old_stderr = sys.stdout, sys.stderr + + stdout_capture = StreamingCapture(self.signals.log_update, None) + stderr_capture = StreamingCapture(self.signals.log_update, None) + + self.capture_streams = [stdout_capture, stderr_capture] + + sys.stdout = stdout_capture + sys.stderr = stderr_capture + + try: + zucaro_cli.main(args=shlex.split(command), standalone_mode=False) + except AbortException: + self.signals.launch_aborted.emit() + except SystemExit: + pass + except Exception as e: + self.signals.log_update.emit(f"Error: {str(e)}") + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + if not stdout_capture.abort_requested: + self.signals.launch_complete.emit() + + modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')] + for mod in modules_to_remove: + del sys.modules[mod] + gc.collect() + + self.signals.cleanup_done.emit() + + except Exception as e: + try: + self.signals.log_update.emit(f"Error preparing version: {str(e)}") + self.signals.cleanup_done.emit() + except: + pass + + def launch_instance_with_window(command, parent=None): window = LaunchWindow(parent) window.launch_game(command) window.exec_() return window + +def prepare_version_with_window(version, parent=None): + window = PrepareWindow(parent) + window.prepare_version(version) + result = window.exec_() + return window.success diff --git a/picodulce.py b/picodulce.py index d11593b..d3306e7 100644 --- a/picodulce.py +++ b/picodulce.py @@ -1402,21 +1402,7 @@ class zucaroVersionSelector(QWidget): dialog.finished.connect(self.populate_installed_versions) dialog.exec_() -class DownloadThread(QThread): - completed = pyqtSignal(bool, str) - def __init__(self, version): - super().__init__() - self.version = version - - def run(self): - try: - command = f"version prepare {self.version}" - modulecli.run_command(command) - self.completed.emit(True, f"Version {self.version} prepared successfully!") - except Exception as e: - error_message = f"Error preparing {self.version}: {str(e)}" - self.completed.emit(False, error_message) class ModLoaderAndVersionMenu(QDialog): def __init__(self, parent=None): @@ -1795,39 +1781,12 @@ class ModLoaderAndVersionMenu(QDialog): def update_download_button_state(self): self.download_button.setEnabled(self.version_combo.currentIndex() != -1) - def show_popup(self): - self.popup = QDialog(self) - self.popup.setWindowTitle("Installing Version") - layout = QVBoxLayout(self.popup) - - label = QLabel("The version is being installed...") - layout.addWidget(label) - - movie = QMovie("drums.gif") - gif_label = QLabel() - gif_label.setMovie(movie) - layout.addWidget(gif_label) - - movie.start() - self.popup.setGeometry(200, 200, 300, 200) - self.popup.setWindowModality(Qt.ApplicationModal) - self.popup.show() - def download_version(self, version): - # Show the popup in the main thread - self.show_popup() - - self.download_thread = DownloadThread(version) - self.download_thread.completed.connect(self.on_download_completed) - self.download_thread.start() - - def on_download_completed(self, success, message): - self.popup.close() + success = loaddaemon.prepare_version_with_window(version, self) if success: - QMessageBox.information(self, "Success", message) + QMessageBox.information(self, "Success", f"Version {version} prepared successfully!") else: - QMessageBox.critical(self, "Error", message) - logging.error(message) + QMessageBox.critical(self, "Error", f"Failed to prepare version {version}.") def populate_available_releases(self, version_combo, install_forge, install_fabric): try: