mirror of
https://github.com/xgui4/LCE-Qt-Launcher.git
synced 2026-07-18 04:50:38 +00:00
Refactor application structure by removing BuildInfo class and integrating its attributes directly into the application context. Update features and views to utilize new string constants for application metadata. Replace UserPref class with a new implementation in pref.py, streamlining user preference management. Enhance instance manager and launcher views to accommodate these changes, ensuring consistent access to application information. Remove deprecated preferences module and adjust related functionality across the codebase.
389 lines
14 KiB
Python
389 lines
14 KiB
Python
# TODO : Make Instance mode indpendant
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from lce_qt_launcher.app_context import AppContext
|
|
|
|
from lce_qt_launcher import instance_extension_str
|
|
import lce_qt_launcher.views.term_service as term_service
|
|
|
|
from PySide6.QtNetwork import QNetworkReply
|
|
from PySide6.QtCore import QObject
|
|
from PySide6.QtWidgets import QMessageBox
|
|
|
|
from enum import Enum
|
|
from subprocess import TimeoutExpired
|
|
|
|
import subprocess
|
|
import json
|
|
import os
|
|
|
|
SCHEME_VERSION = "https://raw.githubusercontent.com/xgui4/LCE-Qt-Launcher/refs/heads/beta/schemas/x-application-lce_inst.json"
|
|
|
|
|
|
class InstanceSource(Enum):
|
|
"""_summary_ The 4 Type of Instances (2 functional, the othes coming soon)"""
|
|
|
|
GITHUB_RELEASE = 0
|
|
FORGEJO_RELEASE = 1
|
|
REMOTE_GIT_SOURCE = 2
|
|
LOCAL_INSTALLATION = 3
|
|
LOCAL_SOURCE_CODE = 4
|
|
|
|
|
|
def from_int_to_InstanceSource(value: int) -> InstanceSource:
|
|
"""_summary_ Convert Into to Instance Source
|
|
|
|
Args:
|
|
value (int): _description_ the int to convert
|
|
|
|
Raises:
|
|
RuntimeError: _description_ Raise if a incorrect int is specified
|
|
|
|
Returns:
|
|
InstanceSource: _description_ The InstanceSource from the int
|
|
"""
|
|
match value:
|
|
case 0:
|
|
return InstanceSource.GITHUB_RELEASE
|
|
case 1:
|
|
return InstanceSource.FORGEJO_RELEASE
|
|
case 2:
|
|
return InstanceSource.REMOTE_GIT_SOURCE
|
|
case 3:
|
|
return InstanceSource.LOCAL_INSTALLATION
|
|
case 4:
|
|
return InstanceSource.LOCAL_SOURCE_CODE
|
|
case _:
|
|
raise RuntimeError(f"{value} is an Incorrect InstanceSource Type")
|
|
|
|
|
|
def from_str_to_InstanceSource(string: str) -> InstanceSource:
|
|
"""_summary_ Convert str to Instance Source
|
|
|
|
Args:
|
|
string (str): _description_ the string to convert
|
|
|
|
Raises:
|
|
RuntimeError: _description_ Raise if a incorrect str is specified
|
|
|
|
Returns:
|
|
InstanceSource: _description_ The InstanceSource from the str
|
|
"""
|
|
match string:
|
|
case "InstanceSource.GITHUB_RELEASE" | "0":
|
|
return InstanceSource.GITHUB_RELEASE
|
|
case "InstanceSource.FORGEJO_RELEASE" | "1":
|
|
return InstanceSource.FORGEJO_RELEASE
|
|
case "InstanceSource.REMOTE_GIT_SOURCE" | "2":
|
|
return InstanceSource.REMOTE_GIT_SOURCE
|
|
case "InstanceSource.LOCAL_INSTALLATION" | "3":
|
|
return InstanceSource.LOCAL_INSTALLATION
|
|
case "InstanceSource.LOCAL_SOURCE_CODE" | "4":
|
|
return InstanceSource.LOCAL_SOURCE_CODE
|
|
case _:
|
|
raise RuntimeError(f"{string} is an Incorrect InstanceSource Type")
|
|
|
|
|
|
class InstanceType(Enum):
|
|
"""_summary_ The instance Type (no function yet)"""
|
|
|
|
CLIENT_VANILLA = 0
|
|
CLENT_MODDED = 1
|
|
SERVER_VANILLA = 2
|
|
SERVER_MODDED = 3
|
|
|
|
|
|
def from_int_to_InstanceType(value: int) -> InstanceType:
|
|
"""_summary_ Convert Into to Instance Type
|
|
|
|
Args:
|
|
value (int): _description_ the int to convert
|
|
|
|
Raises:
|
|
RuntimeError: _description_ Raise if a incorrect int is specified
|
|
|
|
Returns:
|
|
InstanceType: _description_ The InstanceType from the int
|
|
"""
|
|
match value:
|
|
case 0:
|
|
return InstanceType.CLIENT_VANILLA
|
|
case 1:
|
|
return InstanceType.CLENT_MODDED
|
|
case 2:
|
|
return InstanceType.SERVER_VANILLA
|
|
case 3:
|
|
return InstanceType.SERVER_MODDED
|
|
case _:
|
|
raise RuntimeError(f"{value} is an Incorrect InstanceType Type")
|
|
|
|
|
|
def from_str_to_InstanceType(string: str) -> InstanceType:
|
|
"""_summary_ Convert str to Instance Type
|
|
|
|
Args:
|
|
string (str): _description_ the string to convert
|
|
|
|
Raises:
|
|
RuntimeError: _description_ Raise if a incorrect str is specified
|
|
|
|
Returns:
|
|
InstanceSource: _description_ The InstanceType from the str
|
|
"""
|
|
match string:
|
|
case "InstanceType.CLIENT_VANILLA":
|
|
return InstanceType.CLIENT_VANILLA
|
|
case "InstanceType.CLENT_MODDED":
|
|
return InstanceType.CLENT_MODDED
|
|
case "InstanceType.SERVER_VANILLA":
|
|
return InstanceType.SERVER_VANILLA
|
|
case "InstanceType.SERVER_MODDED":
|
|
return InstanceType.SERVER_MODDED
|
|
case _:
|
|
raise RuntimeError(f"{string} is an Incorrect Instance Type")
|
|
|
|
|
|
_DEFAULT_INST_NAME = "New Default (pieeebot neoLegacy)"
|
|
_DEFAULT_INSTALLATION_PATH = ".new-default"
|
|
_DEFAULT_USERNAME = "Steve"
|
|
_DEFAULT_ARCHIVE_FILE = "neoLegacyWindows64.zip"
|
|
_DEFAULT_EXE_NAME = "neoLegacyWindows64/Minecraft.Client.exe"
|
|
_DEFAULT_REPO_URL = "https://github.com/neoStudiosLCE/neoLegacy"
|
|
_DEFAULT_INST_SOURCE = InstanceSource.GITHUB_RELEASE
|
|
_DEFAULT_INST_TYPE = InstanceType.CLIENT_VANILLA
|
|
_DEFAULT_INST_SOURCE_STRING = "InstanceSource.GITHUB_RELEASE"
|
|
_DEFAULT_INST_TYPE_STRING = "InstanceType.CLIENT_VANILLA"
|
|
_DEFAULT_IMAGE = ":/assets/lce-hub-ico.png"
|
|
_DEFAULT_NEWS_FEED = ("https://github.com/neoStudiosLCE/neoLegacy/commits/main/")
|
|
_DEFAULT_VERSION = "v1.0.3b"
|
|
|
|
|
|
class Instance(QObject):
|
|
"""_summary_ An config and inform an instance of Minecraft LCE Installed or to install"""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str = _DEFAULT_INST_NAME,
|
|
installation_path: str = _DEFAULT_INSTALLATION_PATH,
|
|
username: str = _DEFAULT_USERNAME,
|
|
exe_name: str = _DEFAULT_EXE_NAME,
|
|
archive_file: str = _DEFAULT_ARCHIVE_FILE,
|
|
repo_url: str = _DEFAULT_REPO_URL,
|
|
image: str = _DEFAULT_IMAGE,
|
|
instance_source: InstanceSource = _DEFAULT_INST_SOURCE,
|
|
instance_type: InstanceType = _DEFAULT_INST_TYPE,
|
|
news_feed: str = _DEFAULT_NEWS_FEED,
|
|
version: str = _DEFAULT_VERSION,
|
|
steam_link: str = "N/A",
|
|
) -> None:
|
|
super().__init__(parent=None, objectName="Instance")
|
|
self.name: str = name
|
|
self.installation_path: str = installation_path
|
|
self.username: str = username
|
|
self.archive_file: str = archive_file
|
|
self.exe_name: str = exe_name
|
|
self.repo_url: str = repo_url
|
|
self.instance_source: InstanceSource = instance_source
|
|
self.instance_type: InstanceType = instance_type
|
|
self.image: str = image
|
|
self.news_feed: str = news_feed
|
|
self.version: str = version
|
|
self.steam_link: str = steam_link
|
|
|
|
def load_inst_from_dict(self, inst_dict: dict[str, str]) -> None:
|
|
"""_summary_ Load A JSON to a empty Instance Object to import it
|
|
|
|
Args:
|
|
inst_dict (dict[str, str]): _description_
|
|
"""
|
|
self.name = inst_dict.get("name", _DEFAULT_INST_NAME)
|
|
self.installation_path = inst_dict.get(
|
|
"installation_path", _DEFAULT_INSTALLATION_PATH
|
|
)
|
|
self.username = inst_dict.get("username", _DEFAULT_USERNAME)
|
|
self.exe_name = inst_dict.get("exe_name", _DEFAULT_EXE_NAME)
|
|
self.archive_file = inst_dict.get("archive_file", _DEFAULT_ARCHIVE_FILE)
|
|
self.repo_url = inst_dict.get("repo_url", _DEFAULT_REPO_URL)
|
|
self.instance_source = from_str_to_InstanceSource(
|
|
inst_dict.get("instances_source", _DEFAULT_INST_SOURCE_STRING)
|
|
)
|
|
self.instance_type = from_str_to_InstanceType(
|
|
inst_dict.get("instance_type", _DEFAULT_INST_TYPE_STRING)
|
|
)
|
|
self.image = inst_dict.get("image", _DEFAULT_IMAGE)
|
|
self.news_feed = inst_dict.get("news_feed", _DEFAULT_NEWS_FEED)
|
|
self.version = inst_dict.get("version", _DEFAULT_VERSION)
|
|
self.steam_link = inst_dict.get("steam_link", "N/A")
|
|
|
|
def get_download_url(self) -> str:
|
|
"""_summary_ Get the download URL of a Instance
|
|
|
|
Raises:
|
|
RuntimeError: _description_ Error ! Local Installation does not have a download URL when trying to obtain a URL from a local installation
|
|
RuntimeError: _description_ Not implemented yet! if the state is not implemented yey.
|
|
|
|
Returns:
|
|
str: _description_
|
|
"""
|
|
download_release_url = "/releases/download/"
|
|
if (
|
|
self.instance_source == InstanceSource.GITHUB_RELEASE
|
|
or self.instance_source == InstanceSource.FORGEJO_RELEASE
|
|
):
|
|
return (
|
|
self.repo_url
|
|
+ download_release_url
|
|
+ self.version
|
|
+ "/"
|
|
+ self.archive_file
|
|
)
|
|
if self.instance_source == InstanceSource.REMOTE_GIT_SOURCE:
|
|
return f"{self.repo_url}.git"
|
|
if self.instance_source == InstanceSource.LOCAL_INSTALLATION:
|
|
raise RuntimeError(
|
|
"Error ! Local Installation does not have a download URL"
|
|
)
|
|
else:
|
|
raise RuntimeError("Not implemented yet!")
|
|
|
|
def to_dict(self) -> dict[str, str]:
|
|
dict_to_return: dict[str, str] = {
|
|
"$schema": SCHEME_VERSION,
|
|
"name": self.name,
|
|
"installation_path": self.installation_path,
|
|
"username": self.username,
|
|
"exe_name": self.exe_name,
|
|
"archive_file": self.archive_file,
|
|
"repo_url": self.repo_url,
|
|
"instance_source": self.instance_source.name,
|
|
"instance_type": self.instance_type.name,
|
|
"image": self.image,
|
|
"news_feed": self.news_feed,
|
|
"version": self.version,
|
|
"steam_link": self.version,
|
|
}
|
|
return dict_to_return
|
|
|
|
def display(self) -> None:
|
|
print(f"Name : {self.name}")
|
|
print(f"=============================")
|
|
print(f"installation path : {self.installation_path}")
|
|
print(f"username : {self.username}")
|
|
print(f"executable name : {self.exe_name}")
|
|
print(f"archive file : {self.archive_file}")
|
|
print(f"repo url : {self.repo_url}")
|
|
print(f"image : {self.image}")
|
|
print(f"instance source : {self.instance_source.value}")
|
|
print(f"instance type : {self.instance_source.value}")
|
|
print(f"news_feed : {self.news_feed}")
|
|
print(f"version : {self.version}")
|
|
print(f"steam link : {self.steam_link}")
|
|
# print(f"servers : {self.servers}")
|
|
|
|
|
|
class InstanceManager:
|
|
"""_summary_ The Manager for Instances objects"""
|
|
|
|
def __init__(self, instance: Instance, appContext: AppContext):
|
|
self.instance: Instance = instance
|
|
from lce_qt_launcher.managers.downloader import Downloader
|
|
|
|
self._downloader: Downloader = Downloader(appContext)
|
|
def play(self) -> str:
|
|
"""_summary_ Launch an Instance
|
|
|
|
Returns:
|
|
str: _description_ error message or status and exit codes
|
|
"""
|
|
return_code: int = 0
|
|
try:
|
|
client_path: str = os.path.join(self.instance.installation_path, self.instance.exe_name)
|
|
try:
|
|
game_process_temp = subprocess.run([client_path, "-name", self.instance.username])
|
|
return_code = game_process_temp.returncode
|
|
except subprocess.SubprocessError as e:
|
|
if os.name == "posix":
|
|
game_process_temp = subprocess.run(["wine", client_path, "-name", self.instance.username])
|
|
return_code = game_process_temp.returncode
|
|
else:
|
|
QMessageBox.critical(None, "Instance Error", str(e.args))
|
|
except TimeoutExpired as err:
|
|
term_service.print_error(f"process of lauching instance {self.instance.name} Failed. Reason : Timeout Expired.\n traceback : {err.with_traceback}")
|
|
return f"process of lauching instance {self.instance.name} Failed. Reason : Timeout Expired.\n traceback : {err.with_traceback}"
|
|
except PermissionError as err:
|
|
term_service.print_error(f"Cannot launch {self.instance.name}. Reason : Permission Denied.\n traceback : {err.with_traceback}")
|
|
return f"Cannot launch {self.instance.name}. Reason : Permission Denied.\n traceback : {err.with_traceback}"
|
|
else:
|
|
return f"Client closed with code {return_code}"
|
|
|
|
def install_instance(self) -> QNetworkReply | str:
|
|
"""_summary_ Install the selected Instance
|
|
|
|
Raises:
|
|
RuntimeWarning: _description_ #TODO : TO REMOVE, no good Reason to be there
|
|
e: _description_ #TODO : TO REMOVE, no good Reason to be there
|
|
|
|
Returns:
|
|
QNetworkReply: _description_ : the QtNetwork Reply Object of the download process
|
|
"""
|
|
try:
|
|
if self.instance.instance_source in [
|
|
InstanceSource.GITHUB_RELEASE,
|
|
InstanceSource.FORGEJO_RELEASE,
|
|
]:
|
|
return self._downloader.download_inst_async(self.instance)
|
|
if self.instance.instance_source == InstanceSource.REMOTE_GIT_SOURCE:
|
|
subprocess.run(["git", "clone", self.instance.repo_url, self.instance.installation_path])
|
|
return "git_operation_finished"
|
|
else:
|
|
raise RuntimeWarning(
|
|
"Not implemented YET"
|
|
) # TODO : implementing other type
|
|
except RuntimeError as e:
|
|
raise e
|
|
|
|
def save_instance(self, save_file: str) -> None:
|
|
"""_summary_ Save the install with a specified save file and location
|
|
|
|
Args:
|
|
save_file (str): _description_ specified save file and location
|
|
"""
|
|
full_save_file = save_file
|
|
json_string: str = ""
|
|
try:
|
|
json_string = json.dumps(
|
|
vars(self.instance),
|
|
indent=4,
|
|
)
|
|
except:
|
|
json_string = json.dumps(obj=vars(self.instance), indent=4, default=str)
|
|
if not save_file.endswith(instance_extension_str):
|
|
full_save_file: str = save_file + instance_extension_str
|
|
with open(file=full_save_file, mode="w") as f:
|
|
_ = f.write(json_string)
|
|
|
|
def load_instance(self, save_file: str) -> None:
|
|
"""_summary_ Load an instance with a specified save file and location
|
|
|
|
#TODO : Check for the any report
|
|
|
|
Args:
|
|
save_file (str): _description_ specified save file and location
|
|
"""
|
|
with open(file=save_file, mode="r") as json_file:
|
|
json_data = json.load(json_file)
|
|
inst_dict: dict[str, str] = json_data
|
|
self.instance.load_inst_from_dict(inst_dict)
|
|
|
|
def is_installable(self) -> bool:
|
|
"""_summary_ #TODO"""
|
|
# Note : Right now the remote git location is not installable via this launcher, it will added in the next version
|
|
if self.instance.instance_source in [
|
|
InstanceSource.FORGEJO_RELEASE,
|
|
InstanceSource.GITHUB_RELEASE
|
|
]: return True
|
|
else:
|
|
return False
|