Add profile manager and fix some issues inside progress dialog.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
@@ -12,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.profile import ProfileManager
|
||||
from manager.widgets import WidgetManager
|
||||
from window import LauncherMainWindow
|
||||
|
||||
@@ -107,6 +110,9 @@ class Launcher(QApplication, LauncherObject):
|
||||
# Widget (Signal, Event)
|
||||
self.widget_manager = WidgetManager()
|
||||
|
||||
# Manager
|
||||
self.profile_manager = ProfileManager(self, self.widget_manager)
|
||||
|
||||
# Window config
|
||||
self.setApplicationName("TestLauncher")
|
||||
self.main_window = LauncherMainWindow(
|
||||
@@ -125,6 +131,10 @@ class Launcher(QApplication, LauncherObject):
|
||||
def exec(self):
|
||||
self.main_window.show()
|
||||
self.main_window.toolbar.update_all()
|
||||
|
||||
# init managers
|
||||
self.profile_manager.profile_load.emit()
|
||||
|
||||
self.exec_()
|
||||
|
||||
def get_icon(self, path: Path):
|
||||
@@ -192,6 +202,10 @@ class Launcher(QApplication, LauncherObject):
|
||||
def log_dir(self):
|
||||
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
|
||||
def resources_dir(self):
|
||||
return self.program_dir / "resources"
|
||||
|
||||
+1
-1
@@ -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/"
|
||||
@@ -143,10 +143,10 @@ def create_profile(version_id: str, version_type: str, java_args: str="", game_d
|
||||
|
||||
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:
|
||||
"""
|
||||
Create a new profile
|
||||
Create or save a new profile
|
||||
:param profiles:
|
||||
:param profile_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
|
||||
|
||||
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:
|
||||
"""
|
||||
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."
|
||||
) 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]:
|
||||
"""
|
||||
Add a new profile
|
||||
@@ -359,11 +390,12 @@ def remove_profile(profiles: dict, profile_id: str):
|
||||
except KeyError:
|
||||
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
|
||||
:param profiles:
|
||||
:param profile_id:
|
||||
:param new_name:
|
||||
:param max_generate_retry:
|
||||
:return:
|
||||
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:
|
||||
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
|
||||
|
||||
raise ProfileKeyNotFoundException(
|
||||
@@ -426,5 +465,18 @@ def convert_profile_data_to_object(profiles: dict, profile_id: str) -> Profile:
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ class ProgressDialog(QDialog):
|
||||
|
||||
@Slot(str, 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:
|
||||
return
|
||||
|
||||
@@ -131,14 +132,16 @@ class ProgressDialog(QDialog):
|
||||
self.close_timer.start(2000)
|
||||
|
||||
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):
|
||||
"""
|
||||
If you want to use this function with a timer. use self.close_timer.start(TIMEOUT)
|
||||
:return:
|
||||
"""
|
||||
QTimer.singleShot(2000, self.cleanup)
|
||||
for task_id in list(self.rows):
|
||||
self.remove_task(task_id)
|
||||
|
||||
|
||||
+582
-39
@@ -1,16 +1,19 @@
|
||||
"""
|
||||
IMPORTANT: Still under construction!
|
||||
"""
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from datetime import datetime
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from core_lib.common.exception import ProfileException
|
||||
from core_lib.game.profile import read_profile_file
|
||||
from core_lib.common.exception import ProfileException, ProfileLoadException, ProfileSaveException
|
||||
from core_lib.game.profile import read_profile_file, is_profiles_valid, \
|
||||
get_profile_sample, create_or_save_profile_file, list_profiles as list_profiles_raw, convert_profile_data_to_object, \
|
||||
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")
|
||||
|
||||
@@ -42,26 +45,40 @@ class CreateProfileRequest:
|
||||
icon: str = ""
|
||||
resolution: tuple[int, int] | None = None
|
||||
|
||||
class _Profile_UnsetType:
|
||||
pass
|
||||
|
||||
_UNSET = _Profile_UnsetType()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UpdateProfileRequest:
|
||||
name: str | None = None
|
||||
version_id: str | None = None
|
||||
version_type: str | None = None
|
||||
game_dir: Path | None = None
|
||||
java_args: str | None = None
|
||||
game_dir: Path | None | _Profile_UnsetType = _UNSET
|
||||
java_args: str | None | _Profile_UnsetType = _UNSET
|
||||
icon: str | None = None
|
||||
resolution: tuple[int, int] | None = None
|
||||
resolution: tuple[int, int] | None | _Profile_UnsetType = _UNSET
|
||||
|
||||
@dataclass(frozen=True)
|
||||
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)
|
||||
class ValidationResult:
|
||||
pass
|
||||
errors: dict[str, str]
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return not self.errors
|
||||
|
||||
class ProfileManager(QObject):
|
||||
profile_load = Signal()
|
||||
profiles_changed = Signal()
|
||||
profile_created = Signal(str)
|
||||
profile_updated = Signal(str)
|
||||
@@ -70,60 +87,586 @@ class ProfileManager(QObject):
|
||||
error_occurred = Signal(str, str) # error_code, message
|
||||
replace_relative_path = Signal(str)
|
||||
|
||||
def __init__(self, app):
|
||||
def __init__(self, app, widget_manager):
|
||||
QObject.__init__(self, 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
|
||||
def profile_path(self):
|
||||
def default_profile_path(self):
|
||||
return Path(self.app.work_dir, DEFAuLT_PROFILE_RELATIVE_PATH)
|
||||
|
||||
# File lifetime
|
||||
def load(self, path: Path=None, return_value=False) -> None | dict:
|
||||
if path is None:
|
||||
path = self.profile_path
|
||||
def create_profiles(self, path: str | Path | None=None) -> bool:
|
||||
path = Path(path) if path is not None else self.default_profile_path
|
||||
|
||||
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)
|
||||
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: ...
|
||||
def save(self) -> None: ...
|
||||
def save_as(self, path: str | Path) -> None: ...
|
||||
def reload(self) -> None: ...
|
||||
self.profiles = result
|
||||
self._path = path
|
||||
self._is_modified = False
|
||||
|
||||
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
|
||||
def list_profiles(self) -> list[ProfileSummary]: ...
|
||||
def get_profile(self, profile_id: str) -> ProfileDetail: ...
|
||||
def contains(self, profile_id: str) -> bool: ...
|
||||
def list_profiles(self) -> list[ProfileSummary]:
|
||||
result = []
|
||||
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
|
||||
def create_profile(self, request: CreateProfileRequest) -> str: ...
|
||||
def update_profile(
|
||||
self, profile_id: str, changes: UpdateProfileRequest
|
||||
) -> None: ...
|
||||
def remove_profile(self, profile_id: str) -> None: ...
|
||||
def duplicate_profile(
|
||||
self, profile_id: str, new_name: str | None = None
|
||||
) -> str: ...
|
||||
def create_profile(self, request: CreateProfileRequest) -> str | None:
|
||||
if not self.is_loaded:
|
||||
result, _ = self.load()
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
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
|
||||
def set_current_profile(self, profile_id: str | None) -> None: ...
|
||||
def get_current_profile_id(self) -> str | None: ...
|
||||
def get_launch_profile(self, profile_id: str) -> LaunchProfile: ...
|
||||
def set_current_profile(self, profile_id: str | None) -> bool:
|
||||
"""
|
||||
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
|
||||
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
|
||||
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
|
||||
def is_loaded(self) -> bool: ...
|
||||
def is_loaded(self) -> bool:
|
||||
return bool(self.profiles) and is_profiles_valid(self.profiles)
|
||||
|
||||
@property
|
||||
def is_dirty(self) -> bool: ...
|
||||
def is_modified(self) -> bool:
|
||||
return self._is_modified
|
||||
@@ -3,8 +3,6 @@ from pathlib import Path
|
||||
from PySide6.QtCore import QSize
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout
|
||||
|
||||
from core_lib.launcher.object import Launcher as LauncherObject
|
||||
|
||||
class CreateProfile(QWidget):
|
||||
def __init__(self, app, parent=None):
|
||||
super(CreateProfile, self).__init__(parent)
|
||||
|
||||
@@ -12,7 +12,6 @@ from core_lib.qt import messagebox
|
||||
from core_lib.qt.progress_dialog import ProgressDialog
|
||||
from core_lib.qt.messagebox import ask_yes_no_with_plain_text
|
||||
from constant import MAIN_REPO_URL
|
||||
from manager.profile import ProfileManager
|
||||
from manager.widgets import AskForRequest, SelectPathRequest
|
||||
from pages.account import AccountPage
|
||||
from pages.create_profile import CreateProfile
|
||||
@@ -128,9 +127,6 @@ class LauncherMainWindow(QMainWindow):
|
||||
else:
|
||||
self.icon_label.setText("Ouch. The icon is missing.")
|
||||
|
||||
# Manager
|
||||
self.profile_manager = ProfileManager(self)
|
||||
|
||||
# Dialog
|
||||
self.download_dialog = ProgressDialog(self)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user