diff --git a/rare/components/tabs/games/game_info/__init__.py b/rare/components/tabs/games/game_info/__init__.py index 51cc46da..d22a0e00 100644 --- a/rare/components/tabs/games/game_info/__init__.py +++ b/rare/components/tabs/games/game_info/__init__.py @@ -83,7 +83,7 @@ class GameMetadataView(QTreeView, SideTabContents): self.set_title.emit(self.rgame.app_title) self.model.clear() try: - self.model.load(view.__dict__) + self.model.load(vars(view)) except Exception as e: pass self.resizeColumnToContents(0) diff --git a/rare/launcher/__init__.py b/rare/launcher/__init__.py index 9d697456..401970e0 100644 --- a/rare/launcher/__init__.py +++ b/rare/launcher/__init__.py @@ -201,7 +201,7 @@ class RareLauncher(RareApp): def send_message(self, message: BaseModel): if self.socket: - self.socket.write(json.dumps(message.__dict__).encode("utf-8")) + self.socket.write(json.dumps(vars(message)).encode("utf-8")) self.socket.flush() else: self.logger.error("Can't send message") diff --git a/rare/models/game.py b/rare/models/game.py index ca09570b..dd4dea65 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -20,7 +20,7 @@ from rare.shared.game_process import GameProcess from rare.shared.image_manager import ImageManager from rare.utils.paths import data_dir, get_rare_executable from rare.utils.steam_grades import get_rating -from rare.utils.config_helper import add_envvar, remove_envvar +from rare.utils.config_helper import add_envvar logger = getLogger("RareGame") @@ -28,7 +28,6 @@ logger = getLogger("RareGame") class RareGame(RareGameSlim): @dataclass class Metadata: - auto_update: bool = False queued: bool = False queue_pos: Optional[int] = None last_played: datetime = datetime.min @@ -41,20 +40,19 @@ class RareGame(RareGameSlim): @classmethod def from_dict(cls, data: Dict): return cls( - auto_update=data.get("auto_update", False), queued=data.get("queued", False), queue_pos=data.get("queue_pos", None), - last_played=datetime.fromisoformat(data["last_played"]) if data.get("last_played", None) else datetime.min, - grant_date=datetime.fromisoformat(data["grant_date"]) if data.get("grant_date", None) else None, + last_played=datetime.fromisoformat(x) if (x := data.get("last_played", None)) else datetime.min, + grant_date=datetime.fromisoformat(x) if (x := data.get("grant_date", None)) else None, steam_appid=data.get("steam_appid", None), steam_grade=data.get("steam_grade", None), - steam_date=datetime.fromisoformat(data["steam_date"]) if data.get("steam_date", None) else datetime.min, + steam_date=datetime.fromisoformat(x) if (x := data.get("steam_date", None)) else datetime.min, tags=data.get("tags", []), ) - def as_dict(self): + @property + def __dict__(self): return dict( - auto_update=self.auto_update, queued=self.queued, queue_pos=self.queue_pos, last_played=self.last_played.isoformat() if self.last_played else datetime.min, @@ -144,13 +142,14 @@ class RareGame(RareGameSlim): def __load_metadata_json() -> Dict: if RareGame.__metadata_json is None: metadata = {} + file = os.path.join(data_dir(), "game_meta.json") try: - with open(os.path.join(data_dir(), "game_meta.json"), "r") as metadata_fh: - metadata = json.load(metadata_fh) + with open(file, "r") as f: + metadata = json.load(f) except FileNotFoundError: - logger.info("Game metadata json file does not exist.") + logger.info("%s does not exist", file) except json.JSONDecodeError: - logger.warning("Game metadata json file is corrupt.") + logger.warning("%s is corrupt", file) finally: RareGame.__metadata_json = metadata return RareGame.__metadata_json @@ -167,9 +166,9 @@ class RareGame(RareGameSlim): with RareGame.__metadata_lock: metadata: Dict = self.__load_metadata_json() # pylint: disable=unsupported-assignment-operation - metadata[self.app_name] = self.metadata.as_dict() - with open(os.path.join(data_dir(), "game_meta.json"), "w") as metadata_json: - json.dump(metadata, metadata_json, indent=2) + metadata[self.app_name] = vars(self.metadata) + with open(os.path.join(data_dir(), "game_meta.json"), "w+") as file: + json.dump(metadata, file, indent=2) def update_game(self): self.game = self.core.get_game( @@ -468,8 +467,10 @@ class RareGame(RareGameSlim): return self.metadata.steam_appid def set_steam_grade(self, appid: int, grade: str) -> None: - if appid or self.steam_appid is None: + if appid and self.steam_appid is None: add_envvar(self.app_name, "SteamAppId", str(appid)) + add_envvar(self.app_name, "SteamGameId", str(appid)) + add_envvar(self.app_name, "STEAM_COMPAT_APP_ID", str(appid)) self.metadata.steam_appid = appid self.metadata.steam_grade = grade self.metadata.steam_date = datetime.utcnow() diff --git a/rare/models/install.py b/rare/models/install.py index 51ccab5c..72041fc9 100644 --- a/rare/models/install.py +++ b/rare/models/install.py @@ -42,7 +42,7 @@ class InstallOptionsModel: def as_install_kwargs(self) -> Dict: return { k: getattr(self, k) - for k in self.__dict__ + for k in vars(self) if k not in ["update", "silent", "create_shortcut", "overlay", "install_prereqs"] } diff --git a/rare/models/launcher.py b/rare/models/launcher.py index edcc5648..a37a4451 100644 --- a/rare/models/launcher.py +++ b/rare/models/launcher.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Dict class Actions: @@ -15,7 +16,7 @@ class BaseModel: app_name: str @classmethod - def from_json(cls, data: dict): + def from_json(cls, data: Dict): return cls( action=data["action"], app_name=data["app_name"] @@ -28,9 +29,9 @@ class FinishedModel(BaseModel): playtime: int # seconds @classmethod - def from_json(cls, data): + def from_json(cls, data: Dict): return cls( - **BaseModel.from_json(data).__dict__, + **vars(BaseModel.from_json(data)), exit_code=data["exit_code"], playtime=data["playtime"], ) @@ -47,7 +48,7 @@ class StateChangedModel(BaseModel): new_state: int @classmethod - def from_json(cls, data): + def from_json(cls, data: Dict): return cls( action=data["action"], app_name=data["app_name"], @@ -60,8 +61,8 @@ class ErrorModel(BaseModel): error_string: str @classmethod - def from_json(cls, data): + def from_json(cls, data: Dict): return cls( - **BaseModel.from_json(data).__dict__, + **vars(BaseModel.from_json(data)), error_string=data["error_string"] ) diff --git a/rare/shared/workers/uninstall.py b/rare/shared/workers/uninstall.py index 98fe21b7..23566cee 100644 --- a/rare/shared/workers/uninstall.py +++ b/rare/shared/workers/uninstall.py @@ -11,7 +11,7 @@ from rare.lgndr.glue.arguments import LgndrUninstallGameArgs from rare.lgndr.glue.monkeys import LgndrIndirectStatus from rare.models.game import RareGame from rare.models.install import UninstallOptionsModel -from rare.utils import config_helper +from rare.utils import config_helper as config from rare.utils.paths import desktop_links_supported, desktop_link_types, desktop_link_path from .worker import Worker @@ -31,7 +31,7 @@ def uninstall_game( logger.info('Removing registry entries...') if platform.system() != "Window": - prefixes = config_helper.get_wine_prefixes() + prefixes = config.get_prefixes() if platform.system() == "Darwin": # TODO: add crossover support pass @@ -65,10 +65,10 @@ def uninstall_game( ) if not keep_config: logger.info("Removing sections in config file") - config_helper.remove_section(rgame.app_name) - config_helper.remove_section(f"{rgame.app_name}.env") + config.remove_section(rgame.app_name) + config.remove_section(f"{rgame.app_name}.env") - config_helper.save_config() + config.save_config() return status.success, status.message diff --git a/rare/utils/config_helper.py b/rare/utils/config_helper.py index d1dbceda..9a7cdbef 100644 --- a/rare/utils/config_helper.py +++ b/rare/utils/config_helper.py @@ -67,9 +67,7 @@ def get_game_envvar(option: str, app_name: Optional[str] = None, fallback: Any = def get_proton_compat_data(app_name: Optional[str] = None, fallback: Any = None) -> str: - _compat = _config.get("default.env", "STEAM_COMPAT_DATA_PATH", fallback=fallback) - if app_name is not None: - _compat = _config.get(f'{app_name}.env', "STEAM_COMPAT_DATA_PATH", fallback=_compat) + _compat = get_game_envvar("STEAM_COMPAT_DATA_PATH", app_name, fallback=fallback) # return os.path.join(_compat, "pfx") if _compat else fallback return _compat @@ -87,10 +85,47 @@ def get_wine_prefixes() -> Set[str]: _prefixes = [] for name, section in _config.items(): pfx = section.get("WINEPREFIX") or section.get("wine_prefix") - if not pfx: - pfx = os.path.join(compatdata, "pfx") if (compatdata := section.get("STEAM_COMPAT_DATA_PATH")) else "" if pfx: _prefixes.append(pfx) _prefixes = [os.path.expanduser(prefix) for prefix in _prefixes] return {p for p in _prefixes if os.path.isdir(p)} + +def get_proton_prefixes() -> Set[str]: + _prefixes = [] + for name, section in _config.items(): + pfx = os.path.join(compatdata, "pfx") if (compatdata := section.get("STEAM_COMPAT_DATA_PATH")) else "" + if pfx: + _prefixes.append(pfx) + _prefixes = [os.path.expanduser(prefix) for prefix in _prefixes] + return {p for p in _prefixes if os.path.isdir(p)} + + +def get_prefixes() -> Set[str]: + return get_wine_prefixes().union(get_proton_prefixes()) + + +def prefix_exists(pfx: str) -> bool: + return os.path.isdir(pfx) and os.path.isfile(os.path.join(pfx, "user.reg")) + + +def get_prefix(app_name: str = "default") -> Optional[str]: + _compat_path = _config.get(f"{app_name}.env", "STEAM_COMPAT_DATA_PATH", fallback=None) + if _compat_path and prefix_exists(_compat_prefix := os.path.join(_compat_path, "pfx")): + return _compat_prefix + + _wine_prefix = _config.get(f"{app_name}.env", "WINEPREFIX", fallback=None) + _wine_prefix = _config.get(app_name, "wine_prefix", fallback=_wine_prefix) + if _wine_prefix and prefix_exists(_wine_prefix): + return _wine_prefix + + _compat_path = _config.get(f"default.env", "STEAM_COMPAT_DATA_PATH", fallback=None) + if _compat_path and prefix_exists(_compat_prefix := os.path.join(_compat_path, "pfx")): + return _compat_prefix + + _wine_prefix = _config.get(f"default.env", "WINEPREFIX", fallback=None) + _wine_prefix = _config.get("default", "wine_prefix", fallback=_wine_prefix) + if _wine_prefix and prefix_exists(_wine_prefix): + return _wine_prefix + + return None diff --git a/rare/utils/paths.py b/rare/utils/paths.py index 45cf02d1..1af6a67f 100644 --- a/rare/utils/paths.py +++ b/rare/utils/paths.py @@ -36,6 +36,12 @@ def lock_file() -> Path: return Path(QStandardPaths.writableLocation(QStandardPaths.TempLocation), "Rare.lock") +def config_dir() -> Path: + # FIXME: This returns ~/.config/Rare/Rare/ for some reason while the settings are in ~/.config/Rare/Rare.conf + # Take the parent for now, but this should be investigated + return Path(QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)).parent + + def data_dir() -> Path: return Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))