Add profile manager and fix some issues inside progress dialog.

This commit is contained in:
wei
2026-07-29 00:03:25 +08:00
parent 07d5b8a378
commit 061535db7c
7 changed files with 658 additions and 52 deletions
+14
View File
@@ -1,4 +1,6 @@
import logging import logging
import os
import sys
from pathlib import Path from pathlib import Path
from typing import Callable from typing import Callable
@@ -12,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.profile import ProfileManager
from manager.widgets import WidgetManager from manager.widgets import WidgetManager
from window import LauncherMainWindow from window import LauncherMainWindow
@@ -107,6 +110,9 @@ class Launcher(QApplication, LauncherObject):
# Widget (Signal, Event) # Widget (Signal, Event)
self.widget_manager = WidgetManager() self.widget_manager = WidgetManager()
# Manager
self.profile_manager = ProfileManager(self, self.widget_manager)
# Window config # Window config
self.setApplicationName("TestLauncher") self.setApplicationName("TestLauncher")
self.main_window = LauncherMainWindow( self.main_window = LauncherMainWindow(
@@ -125,6 +131,10 @@ class Launcher(QApplication, LauncherObject):
def exec(self): def exec(self):
self.main_window.show() self.main_window.show()
self.main_window.toolbar.update_all() self.main_window.toolbar.update_all()
# init managers
self.profile_manager.profile_load.emit()
self.exec_() self.exec_()
def get_icon(self, path: Path): def get_icon(self, path: Path):
@@ -192,6 +202,10 @@ class Launcher(QApplication, LauncherObject):
def log_dir(self): def log_dir(self):
return self.work_dir / "log" return self.work_dir / "log"
@property
def default_game_dir(self) -> Path:
return self.work_dir / "minecraft" if sys.platform.lower() == "darwin" else self.work_dir / ".minecraft"
@property @property
def resources_dir(self): def resources_dir(self):
return self.program_dir / "resources" return self.program_dir / "resources"
+1 -1
View File
@@ -1,2 +1,2 @@
LAUNCHER_VERSION = "alpha_0.0.2" LAUNCHER_VERSION = "0.0.3-alpha"
MAIN_REPO_URL = "https://repo.weispace.net/wei/Launcher/" MAIN_REPO_URL = "https://repo.weispace.net/wei/Launcher/"
+56 -4
View File
@@ -143,10 +143,10 @@ def create_profile(version_id: str, version_type: str, java_args: str="", game_d
return profile, profile_id return profile, profile_id
def create_profile_file(profiles: dict, profile_filepath: Path, profile_file_bak_filepath: Path | None=None, def create_or_save_profile_file(profiles: dict, profile_filepath: Path, profile_file_bak_filepath: Path | None=None,
overwrite=True, create_backup=True) -> None: overwrite=True, create_backup=True) -> None:
""" """
Create a new profile Create or save a new profile
:param profiles: :param profiles:
:param profile_filepath: :param profile_filepath:
:param profile_file_bak_filepath: :param profile_file_bak_filepath:
@@ -298,6 +298,22 @@ def is_profiles_valid(profiles: dict, fix_wrong=False, ignore_invalid_profile=Fa
return not invalid_ids return not invalid_ids
def is_profile_valid(profile_data: dict, fix_wrong=False):
return _check_profile(profile_data, fix_wrong=fix_wrong)
def is_profile_exists(profiles: dict, profile_id: str) -> bool:
if not isinstance(profiles, dict):
logger.warning("Profile type is not valid: {}".format(type(profiles)))
return False
profiles_map: dict = profiles.get("profiles")
if not isinstance(profiles_map, dict):
logger.warning("Profile map type is not valid: {}".format(type(profiles_map)))
return False
return profile_id in profiles_map
def list_profiles(profiles: dict) -> dict: def list_profiles(profiles: dict) -> dict:
""" """
List all profiles that inside profiles data List all profiles that inside profiles data
@@ -316,6 +332,21 @@ def get_profile(profiles: dict, profile_id: str) -> dict:
f"Specified profile id {profile_id} not found." f"Specified profile id {profile_id} not found."
) from exc ) from exc
def set_current_profile_id(profiles: dict, profile_id: str):
"""
Set current profile id
INFO: You should ensure profile id is existing in profiles before calling this function.
:param profiles:
:param profile_id:
:return:
"""
profiles["lastPlayedProfileID"] = profile_id
def get_current_profile_id(profiles: dict) -> str | None:
return profiles.get("lastPlayedProfileID", None)
get_last_played_profile_id = get_current_profile_id
def add_profile(profiles: dict, profile: dict, max_generate_retry=10) -> tuple[dict, str]: def add_profile(profiles: dict, profile: dict, max_generate_retry=10) -> tuple[dict, str]:
""" """
Add a new profile Add a new profile
@@ -359,11 +390,12 @@ def remove_profile(profiles: dict, profile_id: str):
except KeyError: except KeyError:
raise ProfileKeyNotFoundException("Specified profile id {} not found.".format(profile_id)) raise ProfileKeyNotFoundException("Specified profile id {} not found.".format(profile_id))
def duplicate_profile(profiles: dict, profile_id: str, max_generate_retry=10) -> str: def duplicate_profile(profiles: dict, profile_id: str, new_name: str | None=None, max_generate_retry: int=10) -> str:
""" """
Duplicate a profile Duplicate a profile
:param profiles: :param profiles:
:param profile_id: :param profile_id:
:param new_name:
:param max_generate_retry: :param max_generate_retry:
:return: :return:
new_profile_id: str new_profile_id: str
@@ -387,7 +419,14 @@ def duplicate_profile(profiles: dict, profile_id: str, max_generate_retry=10) ->
for p_id in profiles_map: for p_id in profiles_map:
if p_id == profile_id: if p_id == profile_id:
profiles["profiles"][new_profile_id] = deepcopy(profiles_map[profile_id]) new_profile = deepcopy(profiles_map[profile_id])
if new_name is not None:
new_profile["name"] = new_name
else:
new_profile["name"] = f'{new_profile.get("name", "")} Copy'
profiles["profiles"][new_profile_id] = new_profile
return new_profile_id return new_profile_id
raise ProfileKeyNotFoundException( raise ProfileKeyNotFoundException(
@@ -426,5 +465,18 @@ def convert_profile_data_to_object(profiles: dict, profile_id: str) -> Profile:
return item return item
def update_profile(profiles: dict, profile_id: str, profile_data: dict, fix_wrong: bool=False) -> tuple[dict, str]:
if not is_profile_exists(profiles, profile_id):
raise ProfileException(
"Specified profile id {} not found.".format(profile_id)
)
if not is_profile_valid(profile_data, fix_wrong=fix_wrong):
raise ProfileException(
"Provided profile data is not valid."
)
profiles["profiles"][profile_id] = profile_data
return profiles, profile_id
+5 -2
View File
@@ -89,6 +89,7 @@ class ProgressDialog(QDialog):
@Slot(str, str) @Slot(str, str)
def add_task(self, task_id: str, name: str): def add_task(self, task_id: str, name: str):
# Prevent new tasks from being deleted by cleanup timer
if task_id in self.rows: if task_id in self.rows:
return return
@@ -131,14 +132,16 @@ class ProgressDialog(QDialog):
self.close_timer.start(2000) self.close_timer.start(2000)
def all_tasks_finished(self): def all_tasks_finished(self):
return all(row.finished == True for task_id, row in self.rows.items()) return bool(self.rows) and all(
row.finished
for row in self.rows.values()
)
def cleanup(self): def cleanup(self):
""" """
If you want to use this function with a timer. use self.close_timer.start(TIMEOUT) If you want to use this function with a timer. use self.close_timer.start(TIMEOUT)
:return: :return:
""" """
QTimer.singleShot(2000, self.cleanup)
for task_id in list(self.rows): for task_id in list(self.rows):
self.remove_task(task_id) self.remove_task(task_id)
+582 -39
View File
@@ -1,16 +1,19 @@
""" """
IMPORTANT: Still under construction! IMPORTANT: Still under construction!
""" """
import copy
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from PySide6.QtCore import QObject, Signal from PySide6.QtCore import QObject, Signal
from datetime import datetime from datetime import datetime
from PySide6.QtWidgets import QApplication from core_lib.common.exception import ProfileException, ProfileLoadException, ProfileSaveException
from core_lib.game.profile import read_profile_file, is_profiles_valid, \
from core_lib.common.exception import ProfileException get_profile_sample, create_or_save_profile_file, list_profiles as list_profiles_raw, convert_profile_data_to_object, \
from core_lib.game.profile import read_profile_file is_profile_exists, create_profile as create_profile_raw, update_profile as update_profile_raw, is_profile_valid, \
get_current_profile_id, set_current_profile_id, remove_profile, duplicate_profile
from manager.widgets import AskForRequest
DEFAuLT_PROFILE_RELATIVE_PATH = Path("launcher_profiles.json") DEFAuLT_PROFILE_RELATIVE_PATH = Path("launcher_profiles.json")
@@ -42,26 +45,40 @@ class CreateProfileRequest:
icon: str = "" icon: str = ""
resolution: tuple[int, int] | None = None resolution: tuple[int, int] | None = None
class _Profile_UnsetType:
pass
_UNSET = _Profile_UnsetType()
@dataclass(frozen=True) @dataclass(frozen=True)
class UpdateProfileRequest: class UpdateProfileRequest:
name: str | None = None name: str | None = None
version_id: str | None = None version_id: str | None = None
version_type: str | None = None version_type: str | None = None
game_dir: Path | None = None game_dir: Path | None | _Profile_UnsetType = _UNSET
java_args: str | None = None java_args: str | None | _Profile_UnsetType = _UNSET
icon: str | None = None icon: str | None = None
resolution: tuple[int, int] | None = None resolution: tuple[int, int] | None | _Profile_UnsetType = _UNSET
@dataclass(frozen=True) @dataclass(frozen=True)
class LaunchProfile: class LaunchProfile:
pass profile_id: str
version_id: str
version_type: str
game_dir: Path
java_args: str
resolution: tuple[int, int] | None
@dataclass(frozen=True) @dataclass(frozen=True)
class ValidationResult: class ValidationResult:
pass errors: dict[str, str]
@property
def is_valid(self) -> bool:
return not self.errors
class ProfileManager(QObject): class ProfileManager(QObject):
profile_load = Signal()
profiles_changed = Signal() profiles_changed = Signal()
profile_created = Signal(str) profile_created = Signal(str)
profile_updated = Signal(str) profile_updated = Signal(str)
@@ -70,60 +87,586 @@ class ProfileManager(QObject):
error_occurred = Signal(str, str) # error_code, message error_occurred = Signal(str, str) # error_code, message
replace_relative_path = Signal(str) replace_relative_path = Signal(str)
def __init__(self, app): def __init__(self, app, widget_manager):
QObject.__init__(self, app) QObject.__init__(self, app)
self.app = app self.app = app
self.profiles = [] self.widget_manager = widget_manager
self.profiles = {}
self._path: Path | None = None
self._is_modified = False
self.profile_load.connect(
self.load,
)
@property @property
def profile_path(self): def default_profile_path(self):
return Path(self.app.work_dir, DEFAuLT_PROFILE_RELATIVE_PATH) return Path(self.app.work_dir, DEFAuLT_PROFILE_RELATIVE_PATH)
# File lifetime # File lifetime
def load(self, path: Path=None, return_value=False) -> None | dict: def create_profiles(self, path: str | Path | None=None) -> bool:
if path is None: path = Path(path) if path is not None else self.default_profile_path
path = self.profile_path
try: try:
new_profiles = get_profile_sample()
create_or_save_profile_file(new_profiles, path, create_backup=False)
return True
except ProfileSaveException as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to create profile file: {}".format(e.user_message),
)
return False
except Exception as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to create profile file. An unexpected error occurred: {}".format(e),
)
return False
def load(self, path: str | Path | None=None, return_value: bool=False) -> tuple[bool, None | dict]:
"""
Load profiles
:param path: If provided, this path becomes the current profile path after loading succeeds.
:param return_value:
:return:
"""
path = Path(path) if path is not None else self.default_profile_path
try:
# Prevent existing data from being overwritten
if self._is_modified:
request = AskForRequest("ProfileManager", "Would you like to save the existing profile"
" before reload? (If not existing data will be overwritten)")
self.widget_manager.signal.askyesno.emit(request)
if not request.wait(60):
self.widget_manager.signal.show_error_message.emit(
"Timed out while waiting for confirmation. Please try again."
)
return False, None
result = request.result()
if result:
save_result = self.save()
if not save_result:
self.widget_manager.signal.show_error_message.emit(
"Unable to load profile due to saving error."
)
return False, None
# Create new profile if the target profile file does not exist
if not path.exists() and not self.create_profiles(path):
return False, None
result = read_profile_file(path) result = read_profile_file(path)
except ProfileException:
pass
# Profile validation
if not is_profiles_valid(result):
self.widget_manager.signal.show_error_message.emit(
"Invalid profile file. Try restarting the profile in settings page.",
)
return False, None
# def load(self, path: str | Path) -> None: ... self.profiles = result
def save(self) -> None: ... self._path = path
def save_as(self, path: str | Path) -> None: ... self._is_modified = False
def reload(self) -> None: ...
self.profiles_changed.emit()
if return_value:
return True, copy.deepcopy(result)
return True, None
except (ProfileException, ProfileLoadException) as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to load profile file: {}".format(e.user_message),
)
return False, None
except Exception as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to load profile file. An unexpected error occurred: {}".format(e),
)
return False, None
def save(self, path: str | Path | None=None, create_backup: bool=True) -> bool:
path = Path(path) if path is not None else self.path
try:
if not is_profiles_valid(self.profiles):
self.widget_manager.signal.show_error_message.emit(
"Current profile store in memory is not a valid profile.",
)
return False
create_or_save_profile_file(
self.profiles,
path,
create_backup=create_backup,
)
self._path = path
self._is_modified = False
return True
except (ProfileException, ProfileSaveException) as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to save profile file: {}".format(e.user_message),
)
return False
except Exception as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to save profile file. An unexpected error occurred: {}".format(e),
)
return False
def reload(self) -> tuple[bool, dict | None]:
return self.load(path=self.path)
# Profile search # Profile search
def list_profiles(self) -> list[ProfileSummary]: ... def list_profiles(self) -> list[ProfileSummary]:
def get_profile(self, profile_id: str) -> ProfileDetail: ... result = []
def contains(self, profile_id: str) -> bool: ... profiles = list_profiles_raw(self.profiles)
for profile_id in profiles:
profile = convert_profile_data_to_object(self.profiles, profile_id)
profile_summary = ProfileSummary(
icon=str(profile.icon),
id=profile_id,
last_used=profile.last_used,
name=profile.name,
version_id=profile.version_id,
version_type=profile.type
)
result.append(profile_summary)
return result
def get_profile(self, profile_id: str) -> ProfileDetail | None:
if not self.contains(profile_id, show_error=True):
return None
profile = convert_profile_data_to_object(
self.profiles,
profile_id,
)
resolution = None
if profile.resolution:
# Put resolution data to profile detail
resolution = (
profile.resolution["width"],
profile.resolution["height"],
)
return ProfileDetail(
id=profile_id,
name=profile.name,
version_id=profile.version_id,
version_type=profile.type,
icon=str(profile.icon),
last_used=profile.last_used,
game_dir=Path(profile.game_dir) if profile.game_dir else None,
java_args=profile.java_args,
resolution=resolution,
created=profile.created,
)
def contains(self, profile_id: str, show_error: bool=False) -> bool:
result = is_profile_exists(self.profiles, profile_id)
if not result and show_error:
self.widget_manager.signal.show_error_message.emit(
"Profile ID {} does not exist.".format(profile_id),
)
return result
# CRUD # CRUD
def create_profile(self, request: CreateProfileRequest) -> str: ... def create_profile(self, request: CreateProfileRequest) -> str | None:
def update_profile( if not self.is_loaded:
self, profile_id: str, changes: UpdateProfileRequest result, _ = self.load()
) -> None: ...
def remove_profile(self, profile_id: str) -> None: ... if not result:
def duplicate_profile( return None
self, profile_id: str, new_name: str | None = None
) -> str: ... validation: ValidationResult = self.validate(request)
if not validation.is_valid:
message = "\n".join(validation.errors.values())
self.widget_manager.signal.show_error_message.emit(message)
return None
# Check game dir
if request.game_dir is not None:
game_dir = Path(request.game_dir).as_posix()
else:
default_game_dir = self.app.default_game_dir
game_dir = default_game_dir.as_posix()
try:
# Apply resolution
resolution = None
if request.resolution is not None:
width, height = request.resolution
resolution = {
"width": width,
"height": height,
}
# Copy profiles before update
updated_profiles = copy.deepcopy(self.profiles)
_, profile_id = create_profile_raw(
version_id=request.version_id,
version_type=request.version_type,
java_args=request.java_args,
game_dir=game_dir,
icon=request.icon,
name=request.name,
resolution=resolution,
profiles=updated_profiles,
)
if not is_profiles_valid(updated_profiles):
raise ProfileException("Created profile data is invalid.")
self.profiles = updated_profiles
# Update change event and flag
self._is_modified = True
self.profile_created.emit(profile_id)
self.profiles_changed.emit()
return profile_id
except ProfileException as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to create profile: {e.user_message}"
)
return None
except (TypeError, ValueError) as e:
self.widget_manager.signal.show_error_message.emit(
f"Invalid profile request: {e}"
)
return None
def update_profile(self, profile_id: str, changes: UpdateProfileRequest) -> bool:
# Check profile exists
if not self.contains(profile_id, show_error=True):
return False
try:
# Copy before the actual update.
updated_profiles = copy.deepcopy(self.profiles)
profile_data = copy.deepcopy(
list_profiles_raw(updated_profiles)[profile_id]
)
# Check profile value change
if changes.name is not None:
profile_data["name"] = changes.name
if changes.version_id is not None:
profile_data["lastVersionId"] = changes.version_id
if changes.version_type is not None:
profile_data["type"] = changes.version_type
if changes.game_dir is not _UNSET:
if changes.game_dir is None:
profile_data["gameDir"] = ""
else:
profile_data["gameDir"] = Path(
changes.game_dir
).as_posix()
if changes.java_args is not _UNSET:
profile_data["javaArgs"] = (
"" if changes.java_args is None
else changes.java_args
)
if changes.icon is not None:
profile_data["icon"] = changes.icon
if changes.resolution is not _UNSET:
if changes.resolution is None:
profile_data.pop("resolution", None)
else:
width, height = changes.resolution
profile_data["resolution"] = {
"width": width,
"height": height,
}
update_profile_raw(
updated_profiles,
profile_id,
profile_data,
)
if profile_data == list_profiles_raw(self.profiles)[profile_id]:
return True
# Update event and flag
self.profiles = updated_profiles
self._is_modified = True
self.profile_updated.emit(profile_id)
self.profiles_changed.emit()
return True
except (ProfileException, TypeError, ValueError) as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to update profile: {e}"
)
return False
def remove_profile(self, profile_id: str) -> bool:
if not self.contains(profile_id, show_error=True):
return False
try:
updated_profiles = copy.deepcopy(self.profiles)
is_current_changed = get_current_profile_id(self.profiles) == profile_id
# !!!COPY BEFORE REMOVE!!!
remove_profile(updated_profiles, profile_id)
# Unset current profile id if is same as removed profile's id
if is_current_changed:
updated_profiles["lastPlayedProfileID"] = None
self.profiles = updated_profiles
# event
self._is_modified = True
self.profile_removed.emit(profile_id)
if is_current_changed:
self.current_profile_changed.emit(None)
self.profiles_changed.emit()
return True
except ProfileException as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to remove profile: {e.user_message}"
)
return False
except Exception as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to remove profile. An unknown error occurred: {e}"
)
return False
def duplicate_profile(self, profile_id: str, new_name: str | None = None) -> str | None:
if not self.contains(profile_id, show_error=True):
return None
try:
updated_profiles = copy.deepcopy(self.profiles)
new_profile_id = duplicate_profile(
updated_profiles,
profile_id,
new_name,
)
# Update event and flag
self.profiles = updated_profiles
self._is_modified = True
self.profile_created.emit(new_profile_id)
self.profiles_changed.emit()
return new_profile_id
except ProfileException as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to duplicate profile: {e.user_message}"
)
except Exception as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to duplicate profile. An unknown error occurred: {e}"
)
# Profile switch # Profile switch
def set_current_profile(self, profile_id: str | None) -> None: ... def set_current_profile(self, profile_id: str | None) -> bool:
def get_current_profile_id(self) -> str | None: ... """
def get_launch_profile(self, profile_id: str) -> LaunchProfile: ... Set the current profile ID.
:param profile_id: INFO: Set the current profile ID to None if you want to unset it
:return:
"""
if not self.is_loaded:
success, _ = self.load()
if not success:
return False
if profile_id is not None and not self.contains(profile_id, show_error=True):
return False
try:
existing_id = get_current_profile_id(self.profiles)
# ignore same id request
if profile_id == existing_id:
return True
if existing_id is not None:
self.app.logger.debug(
"Replacing current profile ID {} to {}".format(existing_id, profile_id)
)
set_current_profile_id(self.profiles, profile_id)
# event and flag update
self._is_modified = True
self.current_profile_changed.emit(profile_id)
self.profiles_changed.emit()
return True
except ProfileException as e:
self.widget_manager.signal.show_error_message.emit(
f"Unable to set current profile ID: {e.user_message}"
)
return False
except Exception as e:
self.widget_manager.signal.show_error_message.emit(
"Unable to set current profile ID. An unknown error occurred {}".format(str(e))
)
return False
def get_current_profile_id(self) -> str | None:
if not self.is_loaded:
success, _ = self.load()
if not success:
return None
return get_current_profile_id(self.profiles)
def get_launch_profile(self, profile_id: str) -> LaunchProfile | None:
profile = self.get_profile(profile_id)
if profile is None:
return None
# Use default game_dir if is unset
game_dir = (
profile.game_dir
if profile.game_dir is not None
else self.app.default_game_dir
)
return LaunchProfile(
profile_id=profile.id,
version_id=profile.version_id,
version_type=profile.version_type,
game_dir=game_dir,
java_args=profile.java_args,
resolution=profile.resolution,
)
# Validation # Validation
def validate(self, request: CreateProfileRequest) -> ValidationResult: ... @staticmethod
def validate(request: CreateProfileRequest) -> ValidationResult:
errors = {}
if not isinstance(request.name, str) or not request.name.strip():
errors["name"] = "Profile name cannot be empty."
if not isinstance(request.version_id, str) or not request.version_id.strip():
errors["version_id"] = "Version ID cannot be empty."
if not isinstance(request.version_type, str) or not request.version_type.strip():
errors["version_type"] = "Version type cannot be empty."
if not isinstance(request.java_args, str):
errors["java_args"] = "Java arguments must be a string."
if not isinstance(request.icon, str):
errors["icon"] = "Icon must be a string."
if request.game_dir is not None:
try:
Path(request.game_dir)
except (TypeError, ValueError):
errors["game_dir"] = "Game directory is invalid."
if request.resolution is not None:
if not isinstance(request.resolution, tuple) or len(request.resolution) != 2:
errors["resolution"] = (
"Resolution must contain width and height."
)
else:
width, height = request.resolution
if (not isinstance(width, int) or isinstance(width, bool) or width <= 0 or
not isinstance(height, int) or isinstance(height, bool) or height <= 0):
errors["resolution"] = (
"Resolution width and height must be positive integers."
)
return ValidationResult(errors=errors)
@staticmethod
def validate_update(request: UpdateProfileRequest) -> ValidationResult:
errors = {}
if not isinstance(request.name, str) or not request.name.strip():
errors["name"] = "Profile name cannot be empty."
if not isinstance(request.version_id, str) or not request.version_id.strip():
errors["version_id"] = "Version ID cannot be empty."
if not isinstance(request.version_type, str) or not request.version_type.strip():
errors["version_type"] = "Version type cannot be empty."
if not isinstance(request.java_args, str):
errors["java_args"] = "Java arguments must be a string."
if not isinstance(request.icon, str):
errors["icon"] = "Icon must be a string."
if request.game_dir is not _UNSET:
try:
Path(request.game_dir)
except (TypeError, ValueError):
errors["game_dir"] = "Game directory is invalid."
if request.game_dir is not _UNSET:
if not isinstance(request.resolution, tuple) or len(request.resolution) != 2:
errors["resolution"] = (
"Resolution must contain width and height."
)
else:
width, height = request.resolution
if (not isinstance(width, int) or isinstance(width, bool) or width <= 0 or
not isinstance(height, int) or isinstance(height, bool) or height <= 0):
errors["resolution"] = (
"Resolution width and height must be positive integers."
)
return ValidationResult(errors=errors)
# Property # Property
@property @property
def path(self) -> Path | None: ... def path(self) -> Path | None:
if self._path is None:
self._path = Path(self.app.work_dir, DEFAuLT_PROFILE_RELATIVE_PATH)
return self._path
@property @property
def is_loaded(self) -> bool: ... def is_loaded(self) -> bool:
return bool(self.profiles) and is_profiles_valid(self.profiles)
@property @property
def is_dirty(self) -> bool: ... def is_modified(self) -> bool:
return self._is_modified
-2
View File
@@ -3,8 +3,6 @@ from pathlib import Path
from PySide6.QtCore import QSize from PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout
from core_lib.launcher.object import Launcher as LauncherObject
class CreateProfile(QWidget): class CreateProfile(QWidget):
def __init__(self, app, parent=None): def __init__(self, app, parent=None):
super(CreateProfile, self).__init__(parent) super(CreateProfile, self).__init__(parent)
-4
View File
@@ -12,7 +12,6 @@ from core_lib.qt import messagebox
from core_lib.qt.progress_dialog import ProgressDialog from core_lib.qt.progress_dialog import ProgressDialog
from core_lib.qt.messagebox import ask_yes_no_with_plain_text 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.profile import ProfileManager
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 CreateProfile
@@ -128,9 +127,6 @@ class LauncherMainWindow(QMainWindow):
else: else:
self.icon_label.setText("Ouch. The icon is missing.") self.icon_label.setText("Ouch. The icon is missing.")
# Manager
self.profile_manager = ProfileManager(self)
# Dialog # Dialog
self.download_dialog = ProgressDialog(self) self.download_dialog = ProgressDialog(self)