Code some part of the create profile page. Now it can load versions and types.

This commit is contained in:
wei
2026-07-30 21:43:19 +08:00
parent 3a4c1f42b2
commit 8d865c92a9
9 changed files with 468 additions and 183 deletions
+3 -2
View File
@@ -14,6 +14,7 @@ from core_lib.qt.hack import move_window_to_center, is_light_theme, \
recolor_icon recolor_icon
from core_lib.qt import messagebox from core_lib.qt import messagebox
from core_lib.launcher.object import Launcher as LauncherObject from core_lib.launcher.object import Launcher as LauncherObject
from manager.game import GameManager
from manager.profile import ProfileManager from manager.profile import ProfileManager
from manager.widgets import WidgetManager from manager.widgets import WidgetManager
from window import LauncherMainWindow from window import LauncherMainWindow
@@ -109,6 +110,7 @@ class Launcher(QApplication, LauncherObject):
# Widget (Signal, Event) # Widget (Signal, Event)
self.widget_manager = WidgetManager() self.widget_manager = WidgetManager()
self.game_manager = GameManager(self)
# Manager # Manager
self.profile_manager = ProfileManager(self, self.widget_manager) self.profile_manager = ProfileManager(self, self.widget_manager)
@@ -116,8 +118,7 @@ class Launcher(QApplication, LauncherObject):
# Window config # Window config
self.setApplicationName("TestLauncher") self.setApplicationName("TestLauncher")
self.main_window = LauncherMainWindow( self.main_window = LauncherMainWindow(
app=self, app=self
widget_manager=self.widget_manager,
) )
self.main_window.setGeometry(0, 0, 800, 600) self.main_window.setGeometry(0, 0, 800, 600)
move_window_to_center(self.main_window) move_window_to_center(self.main_window)
+1 -1
View File
@@ -129,7 +129,7 @@ class ProgressDialog(QDialog):
row.finished = True row.finished = True
if self.all_tasks_finished(): if self.all_tasks_finished():
self.close_timer.start(2000) self.close_timer.start(600)
def all_tasks_finished(self): def all_tasks_finished(self):
return bool(self.rows) and all( return bool(self.rows) and all(
+146
View File
@@ -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)
-5
View File
@@ -1,5 +0,0 @@
from PySide6.QtCore import QObject
class RocketLauncher(QObject):
pass
+16 -16
View File
@@ -1,24 +1,13 @@
import queue import queue
import threading import threading
from PySide6.QtCore import QObject, Signal, Slot from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QProgressDialog
from core_lib.qt import messagebox class SignalTransferItem:
def __init__(self):
self.result_queue = queue.Queue()
class AskForRequest:
def __init__(self, title, message):
self.title = title
self.message = message
self.result_queue = queue.Queue(maxsize=1)
self.wait_event = threading.Event() 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): def wait(self, timeout: int | None = 10):
return self.wait_event.wait(timeout) return self.wait_event.wait(timeout)
@@ -28,12 +17,23 @@ class AskForRequest:
except queue.Empty: except queue.Empty:
return None 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): class SelectPathRequest(AskForRequest):
def __init__(self, title, message, filter_raw=None): def __init__(self, title, message, filter_raw=None):
super().__init__(title, message) super().__init__(title, message)
self.filter = filter_raw self.filter = filter_raw
class WidgetManager(QObject): class WidgetManager(QObject):
def __init__(self): def __init__(self):
super(WidgetManager, self).__init__() super(WidgetManager, self).__init__()
+257 -14
View File
@@ -1,14 +1,39 @@
import threading
from collections.abc import Callable
from pathlib import Path from pathlib import Path
from PySide6.QtCore import QSize from PySide6.QtCore import QSize, Qt, QObject, Signal, Slot
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout 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): def __init__(self, app, parent=None):
super(CreateProfile, self).__init__(parent) super(CreateProfilePage, self).__init__(parent)
self.app = app 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.main_layout = QVBoxLayout()
self.right_layout = QVBoxLayout() self.right_layout = QVBoxLayout()
@@ -19,6 +44,7 @@ class CreateProfile(QWidget):
self.version_type_dropdown = QComboBox() self.version_type_dropdown = QComboBox()
self.version_type_dropdown.setObjectName("Dropdown") self.version_type_dropdown.setObjectName("Dropdown")
# Mod loader
self.mod_loader_icon_label = QLabel() self.mod_loader_icon_label = QLabel()
icon = self.app.get_icon(Path(self.app.icon_dir, "mod_loader_temp.png")) icon = self.app.get_icon(Path(self.app.icon_dir, "mod_loader_temp.png"))
pixmap = icon.pixmap(QSize(100, 100)) pixmap = icon.pixmap(QSize(100, 100))
@@ -27,17 +53,33 @@ class CreateProfile(QWidget):
self.mod_loader_dropdown = QComboBox() self.mod_loader_dropdown = QComboBox()
self.mod_loader_dropdown.setObjectName("Dropdown") 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_type_label = QLabel("Version: Vanilla")
self.version_list_label = QLabel("Select a version to continue:") self.version_list_label = QLabel("Select a version to continue:")
self.version_list = QListWidget() self.version_list = QListWidget()
self.version_list.setObjectName("ListWidget") self.version_list.setObjectName("ListWidget")
# Button
self.create_profile = QPushButton("Create Profile") self.create_profile = QPushButton("Create Profile")
self.create_profile.setObjectName("Button") self.create_profile.setObjectName("Button")
self.refresh_button = QPushButton("Refresh") self.refresh_button = QPushButton("Refresh")
self.refresh_button.setObjectName("Button") self.refresh_button.setObjectName("Button")
# Bind widget
self.main_layout.addWidget(self.version_list_label) self.main_layout.addWidget(self.version_list_label)
self.main_layout.addWidget(self.version_list) self.main_layout.addWidget(self.version_list)
self.operate_layout.addWidget(self.version_list_type_label) 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.refresh_button)
self.operate_layout.addWidget(self.create_profile) 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_version_label)
self.right_layout.addWidget(self.mod_loader_label) self.right_layout.addWidget(
self.right_layout.addWidget(self.mod_loader_dropdown) self.mod_loader_version_list,
self.right_layout.addWidget(self.version_type_label) 1,
self.right_layout.addWidget(self.version_type_dropdown) )
self.right_layout.addStretch() self.mod_loader_version_list.setSizePolicy(
QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding,
)
self.right_layout.setContentsMargins(0, 20, 0, 0) self.right_layout.setContentsMargins(0, 20, 0, 0)
self.setLayout(self.layout) self.setLayout(self.layout)
self.layout.addLayout(self.main_layout) self.content_layout.addLayout(
self.layout.addLayout(self.right_layout) 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)
+7
View File
@@ -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
View File
@@ -2,10 +2,8 @@ import json
import subprocess import subprocess
import textwrap import textwrap
import threading import threading
import time
import traceback import traceback
from pathlib import Path from pathlib import Path
from uuid import uuid4
from PySide6.QtCore import Qt, QObject, Signal, Slot from PySide6.QtCore import Qt, QObject, Signal, Slot
from PySide6.QtWidgets import QPushButton, QVBoxLayout, QPlainTextEdit, \ 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.common import FileObject, download_multiple_files
from core_lib.common.exception import VersionNotSelectedException, UnsupportedPlatformException, \ from core_lib.common.exception import VersionNotSelectedException, UnsupportedPlatformException, \
NativeResolveException, DownloadException, DependencyException, VersionManifestFetchException, \ NativeResolveException, DownloadException, DependencyException, AssetManifestFetchException, \
VersionManifestSaveException, NoSpecifiedVersionKeyException, AssetManifestFetchException, \
AssetManifestSaveException, AssetDataKeyNotFoundException, VersionDataKeyNotFoundException AssetManifestSaveException, AssetDataKeyNotFoundException, VersionDataKeyNotFoundException
from core_lib.game.argument import ArgumentMappings, parse_and_generate_arguments, find_game_and_jvm_args, \ from core_lib.game.argument import ArgumentMappings, parse_and_generate_arguments, find_game_and_jvm_args, \
generate_launch_command 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.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, \ from core_lib.game.version_info import (find_useful_part_in_specific_version_manifest, \
get_all_versions, get_available_version_types, \ get_all_versions, get_available_version_types,
fetch_and_save_version_manifest, get_specific_version_manifest_url_and_hash,
download_core_file) download_core_file)
from core_lib.qt.hack import is_main_thread 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 core_lib.runtime.java import find_java_installations
from manager.game import ManifestManager
from manager.widgets import SelectPathRequest, AskForRequest from manager.widgets import SelectPathRequest, AskForRequest
MAX_DOWNLOAD_ATTEMPTS = 3 MAX_DOWNLOAD_ATTEMPTS = 3
@@ -40,6 +36,7 @@ class TestPage(QWidget):
self.app = app self.app = app
self.parent = parent self.parent = parent
self.widget_mgr = widget_manager self.widget_mgr = widget_manager
self.manifest_mgr: ManifestManager = app.game_manager.manifest
self.resize(800, 600) self.resize(800, 600)
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint) self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
@@ -262,7 +259,7 @@ class TestPage(QWidget):
if not ver: if not ver:
return 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 = ( arguments, asset_index, downloads, java_version, libraries, main_class, legacy_game_args = (
find_useful_part_in_specific_version_manifest(ver_data)) find_useful_part_in_specific_version_manifest(ver_data))
@@ -408,7 +405,7 @@ class TestPage(QWidget):
def _load_all_types(self): def _load_all_types(self):
self.signals.clean_ver_types_dropdown.emit() self.signals.clean_ver_types_dropdown.emit()
data = self.get_cached_version_manifest() data = self.manifest_mgr.get_cached_version_manifest()
if not data: if not data:
return return
@@ -418,7 +415,7 @@ class TestPage(QWidget):
def _load_all_version(self, ver_type): def _load_all_version(self, ver_type):
self.signals.clean_vers_dropdown.emit() self.signals.clean_vers_dropdown.emit()
data = self.get_cached_version_manifest() data = self.manifest_mgr.get_cached_version_manifest()
if not data: if not data:
return return
@@ -428,135 +425,14 @@ class TestPage(QWidget):
) )
self.signals.update_vers_dropdown.emit(vers) 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 ============ # ============ Game Files ============
# #
# INFO: All are not in main thread # INFO: All are not in main thread
def _download_client(self, ver: str): 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: try:
ver_data = self.get_cached_version_manifest(ver) ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
if not ver_data: if not ver_data:
self.widget_mgr.download_signal.failed.emit( self.widget_mgr.download_signal.failed.emit(
@@ -611,11 +487,11 @@ class TestPage(QWidget):
self.widget_mgr.signal.enable_widget.emit(self.button_2) self.widget_mgr.signal.enable_widget.emit(self.button_2)
def _download_libraries(self, ver: str): 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: try:
self.signals.clean_output.emit() 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: if not ver_data:
self.widget_mgr.download_signal.failed.emit( self.widget_mgr.download_signal.failed.emit(
@@ -694,11 +570,11 @@ class TestPage(QWidget):
self.widget_mgr.signal.enable_widget.emit(self.button_3) self.widget_mgr.signal.enable_widget.emit(self.button_3)
def _download_assets(self, ver: str): 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: try:
self.signals.clean_output.emit() 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: if not ver_data:
self.widget_mgr.download_signal.failed.emit( self.widget_mgr.download_signal.failed.emit(
@@ -766,7 +642,7 @@ class TestPage(QWidget):
def _parse_argument(self, ver): def _parse_argument(self, ver):
try: try:
ver_data = self.get_cached_version_manifest(ver) ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
if not ver_data: if not ver_data:
return return
@@ -815,7 +691,7 @@ class TestPage(QWidget):
# , 3 = check assets # , 3 = check assets
# Version manifest # Version manifest
ver_data = self.get_cached_version_manifest(ver) ver_data = self.manifest_mgr.get_cached_version_manifest(ver)
if not ver_data: if not ver_data:
return return
@@ -849,7 +725,7 @@ class TestPage(QWidget):
current_status = 3 current_status = 3
default_assets_dir = Path(self.app.work_dir, "assets") 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): 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" "Downloading missing assets"
) )
@@ -996,7 +872,7 @@ class TestPage(QWidget):
# copy # copy
pending = list(files) 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): for retry_time in range(1, MAX_DOWNLOAD_ATTEMPTS + 1):
if not pending: if not pending:
+21 -4
View File
@@ -14,7 +14,8 @@ from core_lib.qt.messagebox import ask_yes_no_with_plain_text
from constant import MAIN_REPO_URL from constant import MAIN_REPO_URL
from manager.widgets import AskForRequest, SelectPathRequest from manager.widgets import AskForRequest, SelectPathRequest
from pages.account import AccountPage 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 from pages.test import TestPage
class ToolBar(QtWidgets.QToolBar): class ToolBar(QtWidgets.QToolBar):
@@ -97,10 +98,11 @@ class ToolBar(QtWidgets.QToolBar):
self.update() self.update()
class LauncherMainWindow(QMainWindow): 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) super(LauncherMainWindow, self).__init__(parent)
self.app = app self.app = app
self.widget_manager = widget_manager self.widget_manager = app.widget_manager
self.game_manager = app.game_manager
# Main layout # Main layout
self.central = QStackedWidget() self.central = QStackedWidget()
@@ -148,17 +150,23 @@ class LauncherMainWindow(QMainWindow):
widget_manager=self.app.widget_manager widget_manager=self.app.widget_manager
) )
self.launch_profile_page = LaunchProfilePage(
app=self.app,
parent=self,
)
# Account page # Account page
self.account_page = AccountPage(self.app, self) self.account_page = AccountPage(self.app, self)
# Profile page # Profile page
self.create_profile_page = CreateProfile(self.app, self) self.create_profile_page = CreateProfilePage(self.app, self)
# Add page # Add page
self.central.addWidget(self.home_page) self.central.addWidget(self.home_page)
self.central.addWidget(self.test_page) self.central.addWidget(self.test_page)
self.central.addWidget(self.account_page) self.central.addWidget(self.account_page)
self.central.addWidget(self.create_profile_page) self.central.addWidget(self.create_profile_page)
self.central.addWidget(self.launch_profile_page)
# Bind toolbar and center widget # Bind toolbar and center widget
self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self.toolbar) 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 = 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") 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 # Home btn
self.home_button = self.toolbar.add_button(icon=self.app.get_icon(Path(self.app.icon_dir, "home.png"))) 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.home_button)
self.toolbar.addWidget(self.open_test_page) self.toolbar.addWidget(self.open_test_page)
self.toolbar.addWidget(self.open_create_profile_page) self.toolbar.addWidget(self.open_create_profile_page)
self.toolbar.addWidget(self.open_launch_profile_page)
self.toolbar.setMovable(True) self.toolbar.setMovable(True)
self.toolbar.setFloatable(False) self.toolbar.setFloatable(False)
self.toolbar.setAllowedAreas(Qt.ToolBarArea.AllToolBarAreas) 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) 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( self.open_account_page.clicked.connect(
lambda: self.set_current_page(self.account_page, self.open_account_page) lambda: self.set_current_page(self.account_page, self.open_account_page)
) )