Now you can add a version in profile manage page. Add install java button and dialog.
316 lines
12 KiB
Python
316 lines
12 KiB
Python
import threading
|
|
|
|
from PySide6.QtCore import QObject, Signal
|
|
from PySide6.QtWidgets import (
|
|
QComboBox, QDialog, QFormLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QVBoxLayout,
|
|
)
|
|
|
|
from core_lib.game.mod import fabric
|
|
from core_lib.game.mod.mod_core import find_useful_part_in_mod_version_manifest
|
|
from core_lib.game.version_info import get_available_version_types
|
|
|
|
|
|
class AddModdedVersionDialog(QDialog):
|
|
"""
|
|
Create a local mod-loader manifest without creating a profile.
|
|
|
|
This dialog is vibed.
|
|
"""
|
|
|
|
version_created = Signal(str)
|
|
|
|
class WorkerSignals(QObject):
|
|
manifest_loaded = Signal(object, object)
|
|
loader_versions_loaded = Signal(str, object, object)
|
|
version_installed = Signal(object, object)
|
|
|
|
def __init__(self, app, parent=None):
|
|
super().__init__(parent)
|
|
self.app = app
|
|
self.manifest_manager = app.game_manager.manifest
|
|
self.root_manifest: dict = {}
|
|
self.worker_signals = self.WorkerSignals(self)
|
|
|
|
self.setWindowTitle(self.app.tr_text("Add Modded Version"))
|
|
self.setMinimumWidth(480)
|
|
|
|
root = QVBoxLayout(self)
|
|
root.setContentsMargins(20, 18, 20, 18)
|
|
root.setSpacing(14)
|
|
|
|
title = QLabel(self.app.tr_text("Add Modded Version"))
|
|
title.setObjectName("dialogTitle")
|
|
description = QLabel(self.app.tr_text(
|
|
"Create a local mod-loader version manifest without creating a profile."
|
|
))
|
|
description.setWordWrap(True)
|
|
|
|
form = QFormLayout()
|
|
form.setVerticalSpacing(10)
|
|
|
|
self.loader_dropdown = QComboBox()
|
|
self.loader_dropdown.setObjectName("Dropdown")
|
|
self.loader_dropdown.addItem(self.app.tr_text("Fabric"), "fabric")
|
|
|
|
self.minecraft_type_dropdown = QComboBox()
|
|
self.minecraft_type_dropdown.setObjectName("Dropdown")
|
|
self.minecraft_version_dropdown = QComboBox()
|
|
self.minecraft_version_dropdown.setObjectName("Dropdown")
|
|
self.loader_version_dropdown = QComboBox()
|
|
self.loader_version_dropdown.setObjectName("Dropdown")
|
|
|
|
form.addRow(self.app.tr_text("Mod loader"), self.loader_dropdown)
|
|
form.addRow(self.app.tr_text("Minecraft type"), self.minecraft_type_dropdown)
|
|
form.addRow(self.app.tr_text("Minecraft version"), self.minecraft_version_dropdown)
|
|
form.addRow(self.app.tr_text("Loader version"), self.loader_version_dropdown)
|
|
|
|
self.status_label = QLabel()
|
|
self.status_label.setWordWrap(True)
|
|
|
|
buttons = QHBoxLayout()
|
|
self.refresh_button = QPushButton(self.app.tr_text("Refresh"))
|
|
self.cancel_button = QPushButton(self.app.tr_text("Cancel"))
|
|
self.add_button = QPushButton(self.app.tr_text("Add Version"))
|
|
self.add_button.setObjectName("saveProfileButton")
|
|
buttons.addWidget(self.refresh_button)
|
|
buttons.addStretch()
|
|
buttons.addWidget(self.cancel_button)
|
|
buttons.addWidget(self.add_button)
|
|
|
|
root.addWidget(title)
|
|
root.addWidget(description)
|
|
root.addLayout(form)
|
|
root.addWidget(self.status_label)
|
|
root.addLayout(buttons)
|
|
|
|
self.minecraft_type_dropdown.currentIndexChanged.connect(
|
|
self._update_minecraft_versions
|
|
)
|
|
self.minecraft_version_dropdown.currentIndexChanged.connect(
|
|
self._start_load_loader_versions
|
|
)
|
|
self.refresh_button.clicked.connect(self.load_versions)
|
|
self.cancel_button.clicked.connect(self.reject)
|
|
self.add_button.clicked.connect(self.install_selected_version)
|
|
|
|
self.worker_signals.manifest_loaded.connect(self._manifest_loaded)
|
|
self.worker_signals.loader_versions_loaded.connect(
|
|
self._loader_versions_loaded
|
|
)
|
|
self.worker_signals.version_installed.connect(self._version_installed)
|
|
|
|
self.load_versions()
|
|
|
|
@staticmethod
|
|
def _error_text(error: Exception) -> str:
|
|
return str(getattr(error, "user_message", None) or error)
|
|
|
|
def _set_loading(self, loading: bool, status: str = "") -> None:
|
|
self.refresh_button.setEnabled(not loading)
|
|
self.add_button.setEnabled(not loading)
|
|
self.status_label.setText(status)
|
|
|
|
def load_versions(self) -> None:
|
|
self._set_loading(True, self.app.tr_text("Loading Minecraft versions..."))
|
|
self.minecraft_type_dropdown.setEnabled(False)
|
|
self.minecraft_version_dropdown.setEnabled(False)
|
|
self.loader_version_dropdown.setEnabled(False)
|
|
|
|
def worker():
|
|
try:
|
|
manifest = self.manifest_manager.get_cached_version_manifest()
|
|
if not manifest:
|
|
raise RuntimeError("Unable to load the Minecraft version manifest.")
|
|
self.worker_signals.manifest_loaded.emit(manifest, None)
|
|
except Exception as error:
|
|
self.worker_signals.manifest_loaded.emit(None, error)
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
|
|
def _manifest_loaded(self, manifest, error) -> None:
|
|
if error is not None:
|
|
self._set_loading(False, self.app.tr_text(
|
|
"Unable to load versions: {error}", error=self._error_text(error)
|
|
))
|
|
return
|
|
|
|
self.root_manifest = manifest
|
|
try:
|
|
types = get_available_version_types(manifest)
|
|
except Exception as exc:
|
|
self._set_loading(False, self.app.tr_text(
|
|
"Unable to read version types: {error}", error=self._error_text(exc)
|
|
))
|
|
return
|
|
|
|
types = [item for item in types if isinstance(item, str) and item]
|
|
if "release" in types:
|
|
types.remove("release")
|
|
types.insert(0, "release")
|
|
|
|
self.minecraft_type_dropdown.blockSignals(True)
|
|
self.minecraft_type_dropdown.clear()
|
|
self.minecraft_type_dropdown.addItems(types)
|
|
self.minecraft_type_dropdown.blockSignals(False)
|
|
self.minecraft_type_dropdown.setEnabled(True)
|
|
self.minecraft_version_dropdown.setEnabled(True)
|
|
self._update_minecraft_versions()
|
|
|
|
def _update_minecraft_versions(self, _index: int = -1) -> None:
|
|
version_type = self.minecraft_type_dropdown.currentText()
|
|
self.minecraft_version_dropdown.blockSignals(True)
|
|
self.minecraft_version_dropdown.clear()
|
|
|
|
for version in self.root_manifest.get("versions", []):
|
|
if version.get("type") != version_type:
|
|
continue
|
|
version_id = version.get("id")
|
|
if version_id:
|
|
self.minecraft_version_dropdown.addItem(version_id, version_id)
|
|
|
|
self.minecraft_version_dropdown.blockSignals(False)
|
|
self._start_load_loader_versions()
|
|
|
|
def _start_load_loader_versions(self, _index: int = -1) -> None:
|
|
minecraft_version = self.minecraft_version_dropdown.currentData()
|
|
self.loader_version_dropdown.clear()
|
|
self.loader_version_dropdown.setEnabled(False)
|
|
|
|
if not minecraft_version:
|
|
self._set_loading(False, self.app.tr_text("No Minecraft version is available for this type."))
|
|
return
|
|
|
|
self._set_loading(True, self.app.tr_text(
|
|
"Loading Fabric versions for {version}...", version=minecraft_version
|
|
))
|
|
|
|
def worker():
|
|
try:
|
|
loaders = fabric.get_version_support_loader_list(minecraft_version)
|
|
self.worker_signals.loader_versions_loaded.emit(
|
|
minecraft_version, loaders, None
|
|
)
|
|
except Exception as error:
|
|
self.worker_signals.loader_versions_loaded.emit(
|
|
minecraft_version, None, error
|
|
)
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
|
|
def _loader_versions_loaded(self, minecraft_version, loaders, error) -> None:
|
|
# Ignore an obsolete response after the user selected another version.
|
|
if minecraft_version != self.minecraft_version_dropdown.currentData():
|
|
return
|
|
|
|
if error is not None:
|
|
self._set_loading(
|
|
False,
|
|
self.app.tr_text(
|
|
"Unable to load Fabric versions: {error}",
|
|
error=self._error_text(error),
|
|
),
|
|
)
|
|
return
|
|
|
|
self.loader_version_dropdown.clear()
|
|
for loader in loaders:
|
|
loader_version = loader.get("version")
|
|
if not loader_version:
|
|
continue
|
|
display_name = loader_version
|
|
if loader.get("stable") is True:
|
|
display_name += " (stable)"
|
|
self.loader_version_dropdown.addItem(display_name, loader)
|
|
|
|
self.loader_version_dropdown.setEnabled(
|
|
self.loader_version_dropdown.count() > 0
|
|
)
|
|
self._set_loading(False, self.app.tr_text("Select a loader version to continue."))
|
|
|
|
def install_selected_version(self) -> None:
|
|
minecraft_version = self.minecraft_version_dropdown.currentData()
|
|
loader_data = self.loader_version_dropdown.currentData()
|
|
|
|
if not minecraft_version or not isinstance(loader_data, dict):
|
|
self.status_label.setText(self.app.tr_text("Select a Minecraft and loader version."))
|
|
return
|
|
|
|
loader_version = loader_data.get("version")
|
|
if not loader_version:
|
|
self.status_label.setText(self.app.tr_text("The selected loader version is invalid."))
|
|
return
|
|
|
|
self._set_loading(True, self.app.tr_text("Creating the Fabric version manifest..."))
|
|
self.loader_dropdown.setEnabled(False)
|
|
self.minecraft_type_dropdown.setEnabled(False)
|
|
self.minecraft_version_dropdown.setEnabled(False)
|
|
self.loader_version_dropdown.setEnabled(False)
|
|
|
|
def worker():
|
|
try:
|
|
# Cache the parent manifest as well, so the new modded version
|
|
# can be resolved immediately when it is selected for launch.
|
|
parent = self.manifest_manager.get_cached_version_manifest(
|
|
minecraft_version
|
|
)
|
|
if not parent:
|
|
raise RuntimeError(
|
|
f"Unable to cache Minecraft {minecraft_version}."
|
|
)
|
|
|
|
version_id = fabric.get_full_version_string(
|
|
version=minecraft_version,
|
|
loader_version=loader_version,
|
|
)
|
|
|
|
def fetch_manifest(_version_id: str):
|
|
return (
|
|
fabric.get_loader_manifest_from_loader_data(
|
|
minecraft_version, loader_data
|
|
),
|
|
None,
|
|
)
|
|
|
|
manifest = self.manifest_manager.get_cached_custom_version_manifest(
|
|
version_id,
|
|
fetch_manifest_handler=fetch_manifest,
|
|
)
|
|
if not manifest:
|
|
raise RuntimeError("Unable to create the Fabric version manifest.")
|
|
|
|
loader_info = find_useful_part_in_mod_version_manifest(manifest)
|
|
if loader_info["id"] != version_id:
|
|
raise ValueError(
|
|
f"Manifest ID mismatch: expected {version_id}, "
|
|
f"got {loader_info['id']}."
|
|
)
|
|
if loader_info["inheritsFrom"] != minecraft_version:
|
|
raise ValueError(
|
|
"The created manifest inherits from an unexpected "
|
|
"Minecraft version."
|
|
)
|
|
|
|
self.worker_signals.version_installed.emit(version_id, None)
|
|
except Exception as error:
|
|
self.worker_signals.version_installed.emit(None, error)
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
|
|
def _version_installed(self, version_id, error) -> None:
|
|
if error is not None:
|
|
self.loader_dropdown.setEnabled(True)
|
|
self.minecraft_type_dropdown.setEnabled(True)
|
|
self.minecraft_version_dropdown.setEnabled(True)
|
|
self.loader_version_dropdown.setEnabled(True)
|
|
self._set_loading(False, self.app.tr_text(
|
|
"Unable to add version: {error}", error=self._error_text(error)
|
|
))
|
|
return
|
|
|
|
self.status_label.setText(self.app.tr_text(
|
|
"Added version {version_id}.", version_id=version_id
|
|
))
|
|
self.version_created.emit(version_id)
|
|
self.accept()
|