Code some part of the create profile page. Now it can load versions and types.
This commit is contained in:
@@ -14,6 +14,7 @@ from core_lib.qt.hack import move_window_to_center, is_light_theme, \
|
||||
recolor_icon
|
||||
from core_lib.qt import messagebox
|
||||
from core_lib.launcher.object import Launcher as LauncherObject
|
||||
from manager.game import GameManager
|
||||
from manager.profile import ProfileManager
|
||||
from manager.widgets import WidgetManager
|
||||
from window import LauncherMainWindow
|
||||
@@ -109,6 +110,7 @@ class Launcher(QApplication, LauncherObject):
|
||||
|
||||
# Widget (Signal, Event)
|
||||
self.widget_manager = WidgetManager()
|
||||
self.game_manager = GameManager(self)
|
||||
|
||||
# Manager
|
||||
self.profile_manager = ProfileManager(self, self.widget_manager)
|
||||
@@ -116,8 +118,7 @@ class Launcher(QApplication, LauncherObject):
|
||||
# Window config
|
||||
self.setApplicationName("TestLauncher")
|
||||
self.main_window = LauncherMainWindow(
|
||||
app=self,
|
||||
widget_manager=self.widget_manager,
|
||||
app=self
|
||||
)
|
||||
self.main_window.setGeometry(0, 0, 800, 600)
|
||||
move_window_to_center(self.main_window)
|
||||
|
||||
@@ -129,7 +129,7 @@ class ProgressDialog(QDialog):
|
||||
row.finished = True
|
||||
|
||||
if self.all_tasks_finished():
|
||||
self.close_timer.start(2000)
|
||||
self.close_timer.start(600)
|
||||
|
||||
def all_tasks_finished(self):
|
||||
return bool(self.rows) and all(
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
from core_lib.common.common import FileObject
|
||||
from core_lib.common.exception import VersionManifestFetchException, VersionManifestSaveException, \
|
||||
NoSpecifiedVersionKeyException
|
||||
from core_lib.game.version_info import fetch_and_save_version_manifest, get_specific_version_manifest_url_and_hash
|
||||
|
||||
|
||||
class ManifestManager:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.widget_mgr = app.widget_manager
|
||||
|
||||
#
|
||||
# ============ Version Data Cache ============
|
||||
#
|
||||
@property
|
||||
def version_manifest_path(self) -> Path:
|
||||
return Path(self.app.work_dir, "versions", "version_manifest_v2.json")
|
||||
|
||||
def get_specific_version_manifest_path(self, target_version: str) -> Path | None:
|
||||
return Path(self.app.work_dir, "versions", target_version, f"{target_version}.json")
|
||||
|
||||
def switch_between_specific_path(self, specified_ver: str | None = None):
|
||||
if specified_ver is None:
|
||||
path = self.version_manifest_path
|
||||
else:
|
||||
path = self.get_specific_version_manifest_path(specified_ver)
|
||||
|
||||
return path
|
||||
|
||||
def _cache_version_manifest(self, specified_ver: str | None = None, root_manifest: dict | None = None) \
|
||||
-> tuple[bool, dict | None]:
|
||||
data = None
|
||||
path = self.switch_between_specific_path(specified_ver)
|
||||
try:
|
||||
data = fetch_and_save_version_manifest(path, target_version=specified_ver, root_manifest=root_manifest)
|
||||
except VersionManifestFetchException as e:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
e.user_message,
|
||||
)
|
||||
except VersionManifestSaveException as e:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
e.user_message,
|
||||
)
|
||||
except Exception as e:
|
||||
self.widget_mgr.signal.show_and_print_error_message.emit(
|
||||
{
|
||||
"error": traceback.format_exception(e),
|
||||
"title": "Cache Error",
|
||||
"message": "An unexpected error occurred while caching version manifest."
|
||||
}
|
||||
)
|
||||
|
||||
return True if data else False, data
|
||||
|
||||
def _read_cached_version_manifest(self, specified_ver: str = None) -> dict | None:
|
||||
path = self.switch_between_specific_path(specified_ver)
|
||||
if not path.exists():
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
"Version manifest missing after check. Please try again later.",
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
version_manifest = json.loads(path.read_text())
|
||||
return version_manifest
|
||||
except Exception as e:
|
||||
self.widget_mgr.signal.show_and_print_error_message.emit(
|
||||
{
|
||||
"error": traceback.format_exception(e),
|
||||
"title": "Cache Error",
|
||||
"message": "Unable to read version manifest."
|
||||
}
|
||||
)
|
||||
return None
|
||||
|
||||
def get_cached_version_manifest(self, specified_ver: str = None, max_age: int = 3600) -> dict | None:
|
||||
path = self.switch_between_specific_path(specified_ver)
|
||||
file = FileObject(path)
|
||||
sha1 = None
|
||||
root_manifest = None
|
||||
|
||||
if specified_ver:
|
||||
root_manifest = self.get_cached_version_manifest()
|
||||
|
||||
if root_manifest is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
_, sha1 = get_specific_version_manifest_url_and_hash(root_manifest, specified_ver)
|
||||
except NoSpecifiedVersionKeyException as e:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
f"Version {specified_ver} does not exist in version manifest.\n{e.user_message}"
|
||||
)
|
||||
return None
|
||||
|
||||
if file.exists:
|
||||
age = time.time() - path.stat().st_mtime
|
||||
|
||||
# For root manifest
|
||||
if not specified_ver and age < max_age:
|
||||
return self._read_cached_version_manifest()
|
||||
|
||||
# Below are all for specific ver
|
||||
if specified_ver and sha1 is None:
|
||||
self.app.logger.warning("Specified version '{}' hash doesn't exist.".format(specified_ver))
|
||||
|
||||
# Also check sha1
|
||||
if specified_ver and age < max_age and file.verify("sha1", sha1):
|
||||
return self._read_cached_version_manifest(specified_ver)
|
||||
|
||||
result, data = self._cache_version_manifest(specified_ver, root_manifest=root_manifest)
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
def create_download_progress_callback(self, progress_title: str):
|
||||
task_id = uuid4().hex
|
||||
signals = self.widget_mgr.download_signal
|
||||
|
||||
signals.started.emit(task_id, progress_title)
|
||||
|
||||
def callback(name, percent):
|
||||
signals.progress.emit(
|
||||
task_id,
|
||||
f"Downloading {name}",
|
||||
percent,
|
||||
)
|
||||
|
||||
return callback, task_id
|
||||
|
||||
|
||||
class GameManager(QObject):
|
||||
def __init__(self, app, parent=None):
|
||||
super(GameManager, self).__init__(parent)
|
||||
self.app = app
|
||||
self.widget_mgr = app.widget_manager
|
||||
self.manifest = ManifestManager(self.app)
|
||||
@@ -1,5 +0,0 @@
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
|
||||
class RocketLauncher(QObject):
|
||||
pass
|
||||
+16
-16
@@ -1,24 +1,13 @@
|
||||
import queue
|
||||
import threading
|
||||
|
||||
from PySide6.QtCore import QObject, Signal, Slot
|
||||
from PySide6.QtWidgets import QProgressDialog
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from core_lib.qt import messagebox
|
||||
|
||||
|
||||
class AskForRequest:
|
||||
def __init__(self, title, message):
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.result_queue = queue.Queue(maxsize=1)
|
||||
class SignalTransferItem:
|
||||
def __init__(self):
|
||||
self.result_queue = queue.Queue()
|
||||
self.wait_event = threading.Event()
|
||||
|
||||
self.details = None # Set this if flag use_plain_text is enabled
|
||||
|
||||
# flags
|
||||
self.use_plain_text = True
|
||||
|
||||
def wait(self, timeout: int | None = 10):
|
||||
return self.wait_event.wait(timeout)
|
||||
|
||||
@@ -28,12 +17,23 @@ class AskForRequest:
|
||||
except queue.Empty:
|
||||
return None
|
||||
|
||||
class AskForRequest(SignalTransferItem):
|
||||
def __init__(self, title, message):
|
||||
super(AskForRequest, self).__init__()
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.result_queue = queue.Queue(maxsize=1)
|
||||
|
||||
self.details = None # Set this if flag use_plain_text is enabled
|
||||
|
||||
# flags
|
||||
self.use_plain_text = True
|
||||
|
||||
class SelectPathRequest(AskForRequest):
|
||||
def __init__(self, title, message, filter_raw=None):
|
||||
super().__init__(title, message)
|
||||
self.filter = filter_raw
|
||||
|
||||
|
||||
class WidgetManager(QObject):
|
||||
def __init__(self):
|
||||
super(WidgetManager, self).__init__()
|
||||
|
||||
+257
-14
@@ -1,14 +1,39 @@
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QSize
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout
|
||||
from PySide6.QtCore import QSize, Qt, QObject, Signal, Slot
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout, \
|
||||
QSizePolicy
|
||||
|
||||
class CreateProfile(QWidget):
|
||||
from core_lib.game.version_info import get_all_versions, get_available_version_types
|
||||
from manager.game import ManifestManager, GameManager
|
||||
from manager.widgets import SignalTransferItem, WidgetManager
|
||||
|
||||
LOADER_LIST = {
|
||||
"fabric": {
|
||||
"type": "modded",
|
||||
"loadVersionMethod": None,
|
||||
},
|
||||
"Don't Install": {
|
||||
"type": "vanilla",
|
||||
"loadVersionMethod": "official",
|
||||
}
|
||||
}
|
||||
|
||||
class CreateProfilePage(QWidget):
|
||||
def __init__(self, app, parent=None):
|
||||
super(CreateProfile, self).__init__(parent)
|
||||
super(CreateProfilePage, self).__init__(parent)
|
||||
self.app = app
|
||||
|
||||
self.layout = QHBoxLayout()
|
||||
# Manager
|
||||
self.widget_mgr: WidgetManager = app.widget_manager
|
||||
self.game_mgr: GameManager = app.game_manager
|
||||
self.manifest_mgr: ManifestManager = self.game_mgr.manifest
|
||||
|
||||
# Layout
|
||||
self.layout = QVBoxLayout()
|
||||
self.content_layout = QHBoxLayout()
|
||||
|
||||
self.main_layout = QVBoxLayout()
|
||||
self.right_layout = QVBoxLayout()
|
||||
@@ -19,6 +44,7 @@ class CreateProfile(QWidget):
|
||||
self.version_type_dropdown = QComboBox()
|
||||
self.version_type_dropdown.setObjectName("Dropdown")
|
||||
|
||||
# Mod loader
|
||||
self.mod_loader_icon_label = QLabel()
|
||||
icon = self.app.get_icon(Path(self.app.icon_dir, "mod_loader_temp.png"))
|
||||
pixmap = icon.pixmap(QSize(100, 100))
|
||||
@@ -27,17 +53,33 @@ class CreateProfile(QWidget):
|
||||
self.mod_loader_dropdown = QComboBox()
|
||||
self.mod_loader_dropdown.setObjectName("Dropdown")
|
||||
|
||||
# Mod loader layout
|
||||
self.mod_config_layout = QHBoxLayout()
|
||||
self.mod_config_fields_layout = QVBoxLayout()
|
||||
|
||||
self.mod_loader_version_label = QLabel(
|
||||
"Mod Loader Version"
|
||||
)
|
||||
|
||||
self.mod_loader_version_list = QListWidget()
|
||||
self.mod_loader_version_list.setObjectName(
|
||||
"ListWidget"
|
||||
)
|
||||
|
||||
# Version list
|
||||
self.version_list_type_label = QLabel("Version: Vanilla")
|
||||
self.version_list_label = QLabel("Select a version to continue:")
|
||||
self.version_list = QListWidget()
|
||||
self.version_list.setObjectName("ListWidget")
|
||||
|
||||
# Button
|
||||
self.create_profile = QPushButton("Create Profile")
|
||||
self.create_profile.setObjectName("Button")
|
||||
|
||||
self.refresh_button = QPushButton("Refresh")
|
||||
self.refresh_button.setObjectName("Button")
|
||||
|
||||
# Bind widget
|
||||
self.main_layout.addWidget(self.version_list_label)
|
||||
self.main_layout.addWidget(self.version_list)
|
||||
self.operate_layout.addWidget(self.version_list_type_label)
|
||||
@@ -45,17 +87,218 @@ class CreateProfile(QWidget):
|
||||
self.operate_layout.addWidget(self.refresh_button)
|
||||
self.operate_layout.addWidget(self.create_profile)
|
||||
|
||||
self.main_layout.addLayout(self.operate_layout)
|
||||
self.mod_config_fields_layout.addWidget(
|
||||
self.mod_loader_label
|
||||
)
|
||||
self.mod_config_fields_layout.addWidget(
|
||||
self.mod_loader_dropdown
|
||||
)
|
||||
self.mod_config_fields_layout.addWidget(
|
||||
self.version_type_label
|
||||
)
|
||||
self.mod_config_fields_layout.addWidget(
|
||||
self.version_type_dropdown
|
||||
)
|
||||
self.mod_config_fields_layout.addStretch()
|
||||
self.mod_config_layout.addWidget(
|
||||
self.mod_loader_icon_label,
|
||||
alignment=Qt.AlignmentFlag.AlignTop,
|
||||
)
|
||||
self.mod_config_layout.addLayout(
|
||||
self.mod_config_fields_layout
|
||||
)
|
||||
self.right_layout.addLayout(
|
||||
self.mod_config_layout
|
||||
)
|
||||
|
||||
self.right_layout.addWidget(self.mod_loader_icon_label)
|
||||
self.right_layout.addWidget(self.mod_loader_label)
|
||||
self.right_layout.addWidget(self.mod_loader_dropdown)
|
||||
self.right_layout.addWidget(self.version_type_label)
|
||||
self.right_layout.addWidget(self.version_type_dropdown)
|
||||
self.right_layout.addStretch()
|
||||
self.right_layout.addWidget(self.mod_loader_version_label)
|
||||
self.right_layout.addWidget(
|
||||
self.mod_loader_version_list,
|
||||
1,
|
||||
)
|
||||
self.mod_loader_version_list.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding,
|
||||
QSizePolicy.Policy.Expanding,
|
||||
)
|
||||
self.right_layout.setContentsMargins(0, 20, 0, 0)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
self.layout.addLayout(self.main_layout)
|
||||
self.layout.addLayout(self.right_layout)
|
||||
self.content_layout.addLayout(
|
||||
self.main_layout,
|
||||
stretch=2,
|
||||
)
|
||||
self.content_layout.addLayout(
|
||||
self.right_layout,
|
||||
stretch=1,
|
||||
)
|
||||
|
||||
self.layout.addLayout(
|
||||
self.content_layout,
|
||||
stretch=1,
|
||||
)
|
||||
self.layout.addLayout(
|
||||
self.operate_layout,
|
||||
)
|
||||
|
||||
# Signals
|
||||
self.signals = self.PageSignal()
|
||||
|
||||
self.signals.get_selected_type.connect(self.get_selected_type)
|
||||
self.signals.get_selected_version.connect(self.get_selected_version)
|
||||
self.signals.update_type_dropdown.connect(self.update_type_dropdown)
|
||||
self.signals.update_version_dropdown.connect(self.update_version_dropdown)
|
||||
|
||||
# events
|
||||
self.type_list_loading = threading.Event()
|
||||
self.version_list_loading = threading.Event()
|
||||
|
||||
self.init_all()
|
||||
|
||||
class PageSignal(QObject):
|
||||
mod_loader_dropdown_changed = Signal(str)
|
||||
load_version_list = Signal()
|
||||
|
||||
# widget
|
||||
clean_list = Signal(object)
|
||||
update_version_dropdown = Signal(list)
|
||||
update_type_dropdown = Signal(list)
|
||||
update_mod_loader_icon_label = Signal(object)
|
||||
update_mod_loader_dropdown = Signal(list)
|
||||
update_mod_loader_version_list = Signal(list)
|
||||
|
||||
get_selected_version = Signal(object)
|
||||
get_selected_type = Signal(object)
|
||||
|
||||
#
|
||||
# Signal Handler
|
||||
#
|
||||
@staticmethod
|
||||
def clean_list(target_list: QListWidget | QComboBox):
|
||||
if not isinstance(target_list, QListWidget | QComboBox):
|
||||
raise TypeError("Target list is not a QListWidget")
|
||||
|
||||
target_list.clear()
|
||||
|
||||
@Slot(dict)
|
||||
def get_selected_type(self, item: SignalTransferItem):
|
||||
if not isinstance(item, SignalTransferItem):
|
||||
raise TypeError("Item is not a SignalTransferItem")
|
||||
|
||||
selected_type = self.version_type_dropdown.currentText()
|
||||
|
||||
if not selected_type:
|
||||
item.result_queue.put(None)
|
||||
else:
|
||||
item.result_queue.put(selected_type)
|
||||
|
||||
item.wait_event.set()
|
||||
|
||||
@Slot(object)
|
||||
def get_selected_version(self, item: SignalTransferItem):
|
||||
if not isinstance(item, SignalTransferItem):
|
||||
raise TypeError("Item is not a SignalTransferItem")
|
||||
|
||||
selected_item = self.version_list.currentItem()
|
||||
|
||||
if selected_item is None:
|
||||
item.result_queue.put(None)
|
||||
else:
|
||||
item.result_queue.put(selected_item.text())
|
||||
|
||||
item.wait_event.set()
|
||||
|
||||
def update_type_dropdown(self, types: list):
|
||||
# Move release to top
|
||||
if "release" in types:
|
||||
types.pop(types.index("release"))
|
||||
types.insert(0, "release")
|
||||
|
||||
self.version_type_dropdown.addItems(types)
|
||||
|
||||
if len(self.version_type_dropdown.currentText()) == 0:
|
||||
self.version_type_dropdown.setCurrentIndex(0)
|
||||
|
||||
def update_version_dropdown(self, versions: list):
|
||||
ids = []
|
||||
|
||||
for version in versions:
|
||||
ver_id = version.get("id", None)
|
||||
if ver_id is not None:
|
||||
ids.append(ver_id)
|
||||
|
||||
self.version_list.addItems(ids)
|
||||
|
||||
selected_item = self.version_list.currentItem()
|
||||
|
||||
if selected_item is None and len(ids) > 0:
|
||||
self.version_list.setCurrentRow(0)
|
||||
|
||||
#
|
||||
# Thread Starter
|
||||
#
|
||||
def init_all(self):
|
||||
self.start_load_type_list(callback=self.start_load_version_list)
|
||||
|
||||
def start_load_type_list(self, callback=None):
|
||||
thread = threading.Thread(target=self.load_type_list, args=(callback,))
|
||||
thread.start()
|
||||
|
||||
def start_load_version_list(self):
|
||||
thread = threading.Thread(target=self.load_version_list)
|
||||
thread.start()
|
||||
|
||||
def start_load_mod_loader_list_and_version(self):
|
||||
pass
|
||||
|
||||
#
|
||||
# Background Thread
|
||||
#
|
||||
def load_type_list(self, callback: Callable[None]=None):
|
||||
# Cleanup
|
||||
self.signals.clean_list.emit(self.version_type_dropdown)
|
||||
|
||||
manifest = self.manifest_mgr.get_cached_version_manifest()
|
||||
if not manifest:
|
||||
return
|
||||
|
||||
ver_types = get_available_version_types(manifest)
|
||||
|
||||
self.signals.update_type_dropdown.emit(ver_types)
|
||||
|
||||
if callable(callback):
|
||||
callback()
|
||||
|
||||
def load_version_list(self, callback: Callable[Callable[None] | None]=None):
|
||||
# Cleanup
|
||||
self.version_list.clear()
|
||||
|
||||
# Get selected version type
|
||||
request = SignalTransferItem()
|
||||
self.signals.get_selected_type.emit(request)
|
||||
|
||||
if not request.wait(10):
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
"Timeout while waiting for selected type.",
|
||||
)
|
||||
return
|
||||
|
||||
selected_type = request.result()
|
||||
|
||||
if not selected_type:
|
||||
self.widget_mgr.signal.show_warning_message.emit(
|
||||
"No type selected.",
|
||||
)
|
||||
return
|
||||
|
||||
# Fetch (or read) manifest
|
||||
manifest = self.manifest_mgr.get_cached_version_manifest()
|
||||
versions = get_all_versions(
|
||||
manifest,
|
||||
specified_type=selected_type
|
||||
)
|
||||
|
||||
# Update list
|
||||
self.update_version_dropdown(versions)
|
||||
|
||||
if callable(callback):
|
||||
callback(None)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
|
||||
class LaunchProfilePage(QWidget):
|
||||
def __init__(self, app, parent=None):
|
||||
super(LaunchProfilePage, self).__init__(parent)
|
||||
self.app = app
|
||||
+17
-141
@@ -2,10 +2,8 @@ import json
|
||||
import subprocess
|
||||
import textwrap
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from PySide6.QtCore import Qt, QObject, Signal, Slot
|
||||
from PySide6.QtWidgets import QPushButton, QVBoxLayout, QPlainTextEdit, \
|
||||
@@ -13,8 +11,7 @@ from PySide6.QtWidgets import QPushButton, QVBoxLayout, QPlainTextEdit, \
|
||||
|
||||
from core_lib.common.common import FileObject, download_multiple_files
|
||||
from core_lib.common.exception import VersionNotSelectedException, UnsupportedPlatformException, \
|
||||
NativeResolveException, DownloadException, DependencyException, VersionManifestFetchException, \
|
||||
VersionManifestSaveException, NoSpecifiedVersionKeyException, AssetManifestFetchException, \
|
||||
NativeResolveException, DownloadException, DependencyException, AssetManifestFetchException, \
|
||||
AssetManifestSaveException, AssetDataKeyNotFoundException, VersionDataKeyNotFoundException
|
||||
from core_lib.game.argument import ArgumentMappings, parse_and_generate_arguments, find_game_and_jvm_args, \
|
||||
generate_launch_command
|
||||
@@ -22,12 +19,11 @@ from core_lib.game.asset import download_assets, check_assets_exist, find_correc
|
||||
from core_lib.game.library_core import generate_classpath, download_full_libraries, check_full_libraries_exists
|
||||
|
||||
from core_lib.game.version_info import (find_useful_part_in_specific_version_manifest, \
|
||||
get_all_versions, get_available_version_types, \
|
||||
fetch_and_save_version_manifest, get_specific_version_manifest_url_and_hash,
|
||||
get_all_versions, get_available_version_types,
|
||||
download_core_file)
|
||||
from core_lib.qt.hack import is_main_thread
|
||||
from core_lib.qt.progress_dialog import ProgressDialog
|
||||
from core_lib.runtime.java import find_java_installations
|
||||
from manager.game import ManifestManager
|
||||
from manager.widgets import SelectPathRequest, AskForRequest
|
||||
|
||||
MAX_DOWNLOAD_ATTEMPTS = 3
|
||||
@@ -40,6 +36,7 @@ class TestPage(QWidget):
|
||||
self.app = app
|
||||
self.parent = parent
|
||||
self.widget_mgr = widget_manager
|
||||
self.manifest_mgr: ManifestManager = app.game_manager.manifest
|
||||
|
||||
self.resize(800, 600)
|
||||
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
||||
@@ -262,7 +259,7 @@ class TestPage(QWidget):
|
||||
if not ver:
|
||||
return
|
||||
|
||||
ver_data = self.get_cached_version_manifest(ver)
|
||||
ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
|
||||
|
||||
arguments, asset_index, downloads, java_version, libraries, main_class, legacy_game_args = (
|
||||
find_useful_part_in_specific_version_manifest(ver_data))
|
||||
@@ -408,7 +405,7 @@ class TestPage(QWidget):
|
||||
def _load_all_types(self):
|
||||
self.signals.clean_ver_types_dropdown.emit()
|
||||
|
||||
data = self.get_cached_version_manifest()
|
||||
data = self.manifest_mgr.get_cached_version_manifest()
|
||||
if not data:
|
||||
return
|
||||
|
||||
@@ -418,7 +415,7 @@ class TestPage(QWidget):
|
||||
def _load_all_version(self, ver_type):
|
||||
self.signals.clean_vers_dropdown.emit()
|
||||
|
||||
data = self.get_cached_version_manifest()
|
||||
data = self.manifest_mgr.get_cached_version_manifest()
|
||||
if not data:
|
||||
return
|
||||
|
||||
@@ -428,135 +425,14 @@ class TestPage(QWidget):
|
||||
)
|
||||
self.signals.update_vers_dropdown.emit(vers)
|
||||
|
||||
#
|
||||
# ============ Version Data Cache ============
|
||||
#
|
||||
@property
|
||||
def version_manifest_path(self) -> Path:
|
||||
return Path(self.app.work_dir, "versions", "version_manifest_v2.json")
|
||||
|
||||
def get_specific_version_manifest_path(self, target_version: str) -> Path | None:
|
||||
return Path(self.app.work_dir, "versions", target_version, f"{target_version}.json")
|
||||
|
||||
def switch_between_specific_path(self, specified_ver: str | None = None):
|
||||
if specified_ver is None:
|
||||
path = self.version_manifest_path
|
||||
else:
|
||||
path = self.get_specific_version_manifest_path(specified_ver)
|
||||
|
||||
return path
|
||||
|
||||
def _cache_version_manifest(self, specified_ver: str | None = None, root_manifest: dict | None = None) \
|
||||
-> tuple[bool, dict | None]:
|
||||
data = None
|
||||
path = self.switch_between_specific_path(specified_ver)
|
||||
try:
|
||||
data = fetch_and_save_version_manifest(path, target_version=specified_ver, root_manifest=root_manifest)
|
||||
except VersionManifestFetchException as e:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
e.user_message,
|
||||
)
|
||||
except VersionManifestSaveException as e:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
e.user_message,
|
||||
)
|
||||
except Exception as e:
|
||||
self.widget_mgr.signal.show_and_print_error_message.emit(
|
||||
{
|
||||
"error": traceback.format_exception(e),
|
||||
"title": "Cache Error",
|
||||
"message": "An unexpected error occurred while caching version manifest."
|
||||
}
|
||||
)
|
||||
|
||||
return True if data else False, data
|
||||
|
||||
def _read_cached_version_manifest(self, specified_ver: str = None) -> dict | None:
|
||||
path = self.switch_between_specific_path(specified_ver)
|
||||
if not path.exists():
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
"Version manifest missing after check. Please try again later.",
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
version_manifest = json.loads(path.read_text())
|
||||
return version_manifest
|
||||
except Exception as e:
|
||||
self.widget_mgr.signal.show_and_print_error_message.emit(
|
||||
{
|
||||
"error": traceback.format_exception(e),
|
||||
"title": "Cache Error",
|
||||
"message": "Unable to read version manifest."
|
||||
}
|
||||
)
|
||||
return None
|
||||
|
||||
def get_cached_version_manifest(self, specified_ver: str = None, max_age: int = 3600) -> dict | None:
|
||||
path = self.switch_between_specific_path(specified_ver)
|
||||
file = FileObject(path)
|
||||
sha1 = None
|
||||
root_manifest = None
|
||||
|
||||
if specified_ver:
|
||||
root_manifest = self.get_cached_version_manifest()
|
||||
|
||||
if root_manifest is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
_, sha1 = get_specific_version_manifest_url_and_hash(root_manifest, specified_ver)
|
||||
except NoSpecifiedVersionKeyException as e:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
f"Version {specified_ver} does not exist in version manifest.\n{e.user_message}"
|
||||
)
|
||||
return None
|
||||
|
||||
if file.exists:
|
||||
age = time.time() - path.stat().st_mtime
|
||||
|
||||
# For root manifest
|
||||
if not specified_ver and age < max_age:
|
||||
return self._read_cached_version_manifest()
|
||||
|
||||
# Below are all for specific ver
|
||||
if specified_ver and sha1 is None:
|
||||
self.app.logger.warning("Specified version '{}' hash doesn't exist.".format(specified_ver))
|
||||
|
||||
# Also check sha1
|
||||
if specified_ver and age < max_age and file.verify("sha1", sha1):
|
||||
return self._read_cached_version_manifest(specified_ver)
|
||||
|
||||
result, data = self._cache_version_manifest(specified_ver, root_manifest=root_manifest)
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
def create_download_progress_callback(self, progress_title: str):
|
||||
task_id = uuid4().hex
|
||||
signals = self.widget_mgr.download_signal
|
||||
|
||||
signals.started.emit(task_id, progress_title)
|
||||
|
||||
def callback(name, percent):
|
||||
signals.progress.emit(
|
||||
task_id,
|
||||
f"Downloading {name}",
|
||||
percent,
|
||||
)
|
||||
|
||||
return callback, task_id
|
||||
|
||||
#
|
||||
# ============ Game Files ============
|
||||
#
|
||||
# INFO: All are not in main thread
|
||||
def _download_client(self, ver: str):
|
||||
callback, task_id = self.create_download_progress_callback(f"Downloading {ver} client")
|
||||
callback, task_id = self.manifest_mgr.create_download_progress_callback(f"Downloading {ver} client")
|
||||
try:
|
||||
ver_data = self.get_cached_version_manifest(ver)
|
||||
ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
|
||||
|
||||
if not ver_data:
|
||||
self.widget_mgr.download_signal.failed.emit(
|
||||
@@ -611,11 +487,11 @@ class TestPage(QWidget):
|
||||
self.widget_mgr.signal.enable_widget.emit(self.button_2)
|
||||
|
||||
def _download_libraries(self, ver: str):
|
||||
callback, task_id = self.create_download_progress_callback(f"Downloading libraries for version {ver}")
|
||||
callback, task_id = self.manifest_mgr.create_download_progress_callback(f"Downloading libraries for version {ver}")
|
||||
try:
|
||||
self.signals.clean_output.emit()
|
||||
|
||||
ver_data = self.get_cached_version_manifest(ver)
|
||||
ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
|
||||
|
||||
if not ver_data:
|
||||
self.widget_mgr.download_signal.failed.emit(
|
||||
@@ -694,11 +570,11 @@ class TestPage(QWidget):
|
||||
self.widget_mgr.signal.enable_widget.emit(self.button_3)
|
||||
|
||||
def _download_assets(self, ver: str):
|
||||
callback, task_id = self.create_download_progress_callback(f"Downloading assets for version {ver}")
|
||||
callback, task_id = self.manifest_mgr.create_download_progress_callback(f"Downloading assets for version {ver}")
|
||||
try:
|
||||
self.signals.clean_output.emit()
|
||||
|
||||
ver_data = self.get_cached_version_manifest(ver)
|
||||
ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
|
||||
|
||||
if not ver_data:
|
||||
self.widget_mgr.download_signal.failed.emit(
|
||||
@@ -766,7 +642,7 @@ class TestPage(QWidget):
|
||||
|
||||
def _parse_argument(self, ver):
|
||||
try:
|
||||
ver_data = self.get_cached_version_manifest(ver)
|
||||
ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
|
||||
|
||||
if not ver_data:
|
||||
return
|
||||
@@ -815,7 +691,7 @@ class TestPage(QWidget):
|
||||
# , 3 = check assets
|
||||
|
||||
# Version manifest
|
||||
ver_data = self.get_cached_version_manifest(ver)
|
||||
ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
|
||||
|
||||
if not ver_data:
|
||||
return
|
||||
@@ -849,7 +725,7 @@ class TestPage(QWidget):
|
||||
current_status = 3
|
||||
default_assets_dir = Path(self.app.work_dir, "assets")
|
||||
if not check_assets_exist(ver_data, default_assets_dir, launcher_root=self.app.work_dir):
|
||||
callback, task_id = self.create_download_progress_callback(
|
||||
callback, task_id = self.manifest_mgr.create_download_progress_callback(
|
||||
"Downloading missing assets"
|
||||
)
|
||||
|
||||
@@ -996,7 +872,7 @@ class TestPage(QWidget):
|
||||
# copy
|
||||
pending = list(files)
|
||||
|
||||
callback, task_id = self.create_download_progress_callback(f"Redownloading {len(files)} files.")
|
||||
callback, task_id = self.manifest_mgr.create_download_progress_callback(f"Redownloading {len(files)} files.")
|
||||
|
||||
for retry_time in range(1, MAX_DOWNLOAD_ATTEMPTS + 1):
|
||||
if not pending:
|
||||
|
||||
@@ -14,7 +14,8 @@ from core_lib.qt.messagebox import ask_yes_no_with_plain_text
|
||||
from constant import MAIN_REPO_URL
|
||||
from manager.widgets import AskForRequest, SelectPathRequest
|
||||
from pages.account import AccountPage
|
||||
from pages.create_profile import CreateProfile
|
||||
from pages.create_profile import CreateProfilePage
|
||||
from pages.launch_profile import LaunchProfilePage
|
||||
from pages.test import TestPage
|
||||
|
||||
class ToolBar(QtWidgets.QToolBar):
|
||||
@@ -97,10 +98,11 @@ class ToolBar(QtWidgets.QToolBar):
|
||||
self.update()
|
||||
|
||||
class LauncherMainWindow(QMainWindow):
|
||||
def __init__(self, / ,app, widget_manager, parent: QWidget | None=None):
|
||||
def __init__(self, / ,app, parent: QWidget | None=None):
|
||||
super(LauncherMainWindow, self).__init__(parent)
|
||||
self.app = app
|
||||
self.widget_manager = widget_manager
|
||||
self.widget_manager = app.widget_manager
|
||||
self.game_manager = app.game_manager
|
||||
|
||||
# Main layout
|
||||
self.central = QStackedWidget()
|
||||
@@ -148,17 +150,23 @@ class LauncherMainWindow(QMainWindow):
|
||||
widget_manager=self.app.widget_manager
|
||||
)
|
||||
|
||||
self.launch_profile_page = LaunchProfilePage(
|
||||
app=self.app,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
# Account page
|
||||
self.account_page = AccountPage(self.app, self)
|
||||
|
||||
# Profile page
|
||||
self.create_profile_page = CreateProfile(self.app, self)
|
||||
self.create_profile_page = CreateProfilePage(self.app, self)
|
||||
|
||||
# Add page
|
||||
self.central.addWidget(self.home_page)
|
||||
self.central.addWidget(self.test_page)
|
||||
self.central.addWidget(self.account_page)
|
||||
self.central.addWidget(self.create_profile_page)
|
||||
self.central.addWidget(self.launch_profile_page)
|
||||
|
||||
# Bind toolbar and center widget
|
||||
self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self.toolbar)
|
||||
@@ -174,6 +182,10 @@ class LauncherMainWindow(QMainWindow):
|
||||
self.open_test_page = self.toolbar.add_button(" Tests", icon=self.app.get_icon(Path(self.app.icon_dir, "debug.png")))
|
||||
self.open_test_page.setObjectName("open_test_page")
|
||||
|
||||
# Launch profile btn
|
||||
self.open_launch_profile_page = self.toolbar.add_button(" Launch Profile",
|
||||
icon=self.app.get_icon(Path(self.app.icon_dir, "profile_default.png")))
|
||||
|
||||
# Home btn
|
||||
self.home_button = self.toolbar.add_button(icon=self.app.get_icon(Path(self.app.icon_dir, "home.png")))
|
||||
|
||||
@@ -187,6 +199,7 @@ class LauncherMainWindow(QMainWindow):
|
||||
self.toolbar.addWidget(self.home_button)
|
||||
self.toolbar.addWidget(self.open_test_page)
|
||||
self.toolbar.addWidget(self.open_create_profile_page)
|
||||
self.toolbar.addWidget(self.open_launch_profile_page)
|
||||
self.toolbar.setMovable(True)
|
||||
self.toolbar.setFloatable(False)
|
||||
self.toolbar.setAllowedAreas(Qt.ToolBarArea.AllToolBarAreas)
|
||||
@@ -204,6 +217,10 @@ class LauncherMainWindow(QMainWindow):
|
||||
lambda: self.set_current_page(self.test_page, self.open_test_page)
|
||||
)
|
||||
|
||||
self.open_launch_profile_page.clicked.connect(
|
||||
lambda: self.set_current_page(self.launch_profile_page, self.open_launch_profile_page)
|
||||
)
|
||||
|
||||
self.open_account_page.clicked.connect(
|
||||
lambda: self.set_current_page(self.account_page, self.open_account_page)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user