From fa5294b1d551890d54e731bb9948fbeb6abab1e3 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 10 Dec 2023 22:43:31 +0200 Subject: [PATCH] Lgndr: Replace the monkey functions with factories to create them The factories are also usable in Rare's code to create compatible functions for the callbacks. If they there is no callback they just log what is happening. It also removes the need for `typing-extentions` module. --- AppImageBuilder.yml | 2 +- README.md | 59 +++++++++++++++--------------------- rare/lgndr/cli.py | 2 -- rare/lgndr/glue/arguments.py | 30 +++++++++--------- rare/lgndr/glue/monkeys.py | 44 ++++++++++++++++++++++----- rare/models/install.py | 7 +---- requirements-flatpak.txt | 5 +++ requirements-full.txt | 1 - requirements.txt | 1 - setup.py | 1 - 10 files changed, 82 insertions(+), 70 deletions(-) create mode 100644 requirements-flatpak.txt diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 72701c21..dc20b8e1 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -12,7 +12,7 @@ script: # copy Logo - cp AppDir/usr/src/rare/resources/images/Rare.png AppDir/usr/share/icons/hicolor/256x256/apps/ # Install application dependencies - - python3 -m pip install --ignore-installed --prefix=/usr --root=AppDir pypresence qtawesome legendary-gl orjson typing_extensions + - python3 -m pip install --ignore-installed --prefix=/usr --root=AppDir pypresence qtawesome legendary-gl orjson AppDir: path: AppDir diff --git a/README.md b/README.md index 8dec58cb..face2a34 100644 --- a/README.md +++ b/README.md @@ -45,32 +45,36 @@ Run it via: There are some AUR packages available: - [rare](https://aur.archlinux.org/packages/rare) - for stable releases -- [rare-git](https://aur.archlinux.org/packages/rare-git) - for the latest features, which are not in a stable release +- [rare-git](https://aur.archlinux.org/packages/rare-git) - for the latest development version #### Debian based - DUR package: [rare](https://mpr.hunterwittenborn.com/packages/rare) -- .deb file in [releases page](https://github.com/Dummerle/Rare/releases) +- `.deb` file in [releases page](https://github.com/Dummerle/Rare/releases) **Note**: - -- pypresence is an optional package. You can install it - from [DUR](https://mpr.hunterwittenborn.com/packages/python3-pypresence) or with pip. -- Do not wonder if some icons look strange, because the official python3-qtawesome package is too old. Many icons were - replaced. +- pypresence is an optional package. You can install it from [DUR](https://mpr.hunterwittenborn.com/packages/python3-pypresence) or with pip. +- Some icons might look strange on Debian based distributions. The official python3-qtawesome package is too old. ### macOS -There is a .dmg file available in [releases page](https://github.com/Dummerle/Rare/releases). +There is a `.dmg` file available in [releases page](https://github.com/Dummerle/Rare/releases). -**Note**: When you launch it, you will see an error, that the package is from an unknown source. You have to enable it -manually in `Settings -> Security and Privacy`. Otherwise, Gatekeeper will block Rare from running. +**Note**: When you launch it, you will see an error, that the package is from an unknown source. You have to enable it manually in `Settings -> Security and Privacy`. Otherwise, Gatekeeper will block Rare from running. You can also use `pip`. ### Windows +There is an `.msi` installer available in [releases page](https://github.com/Dummerle/Rare/releases). + +There is also a semi-portable `.zip` archive in [releases page](https://github.com/Dummerle/Rare/releases) that lets you run Rare without installing it. + +**Important**: On recent version of Windows you should have MSVC 2015 installed, you can get it from [here](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022) + +#### Packages + - Rare is available as a [Winget package](https://github.com/microsoft/winget-pkgs/tree/master/manifests/d/Dummerle/Rare) You can install Rare with the following one-liner: @@ -81,10 +85,7 @@ You can install Rare with the following one-liner: `choco install rare` -- There is a small beta tool for Windows: [Rare Updater](https://github.com/Dummerle/RareUpdater), which installs and updates rare with a single click - - -*NOTE*: On recent Windows you should have MSVC 2015 installed, you can get it from [here](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022) +- We also have a beta tool for Windows: [Rare Updater](https://github.com/Dummerle/RareUpdater), which installs and updates rare with a single click ### Packages @@ -95,7 +96,8 @@ file for macOS. In the [actions](https://github.com/Dummerle/Rare/actions) tab you can find packages for the latest commits. -**Note**: They might be unstable. +**Note**: They might be unstable and likely broken. + ### Installation via pip (platform independent) @@ -105,22 +107,13 @@ Linux, Mac and FreeBSD: execute `rare` in your terminal. Windows: execute `pythonw -m rare` in cmd -It is possible to create a desktop link, or a start menu link. Execute the command above with `--desktop-shortcut` -or `--startmenu-shortcut` option, alternatively you can create them in the settings. +It is possible to create a desktop link, or a start menu link. Execute the command above with `--desktop-shortcut` or `--startmenu-shortcut` option, alternatively you can create them in the settings. **Note about $PATH**: -On Linux: - -`/home/user/.local/bin` must be in your PATH. - -On Windows: - -`PythonInstallationDirectory\Scripts` must be in your PATH. - -On Mac: - -`/Users/user/Library/Python/3.x/bin` must be in your PATH. +* On Linux `/home/user/.local/bin` must be in your PATH. +* On Windows `PythonInstallationDirectory\Scripts` must be in your PATH. +* On Mac `/Users/user/Library/Python/3.x/bin` must be in your PATH. ### Run from source @@ -128,10 +121,9 @@ On Mac: 1. Clone the repo: `git clone https://github.com/Dummerle/Rare 2. Change your working directory to the project folder: `cd Rare` 3. Run `pip install -r requirements.txt` to install all required dependencies. - If you want to be able to use the automatic login and Discord pypresence, run `pip install -r requirements-full.txt` - If you are on Arch you can - run `sudo pacman --needed -S python-wheel python-setuptools python-pyqt5 python-qtawesome python-requests python-typing_extensions` and `yay -S legendary` - If you are on FreeBSD you have to install py39-qt5 from the packages: `sudo pkg install py39-qt5` + * If you want to be able to use the automatic login and Discord pypresence, run `pip install -r requirements-full.txt` + * If you are on Arch you can run `sudo pacman --needed -S python-wheel python-setuptools python-pyqt5 python-qtawesome python-requests python-orjson` and `yay -S legendary` + * If you are on FreeBSD you have to install py39-qt5 from the packages: `sudo pkg install py39-qt5` 4. Run `python3 -m rare` ## Contributing @@ -139,8 +131,7 @@ On Mac: There are several options to contribute. - If you know Python and PyQt, you can implement new features (Some ideas are in the projects tab). -- You can translate the application in your language: Check our [transifex](https://www.transifex.com/rare-1/rare) page - for that. +- You can translate the application in your language: Check our [transifex](https://www.transifex.com/rare-1/rare) page for that. More information is available in CONTRIBUTING.md. diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py index 5c63e293..cf2fff53 100644 --- a/rare/lgndr/cli.py +++ b/rare/lgndr/cli.py @@ -388,7 +388,6 @@ class LegendaryCLI(LegendaryCLIReal): # Override logger for the local context to use message as part of the indirect return value logger = LgndrIndirectLogger(args.indirect_status, self.logger, logging.WARNING) get_boolean_choice = args.get_boolean_choice_main - # def get_boolean_choice(x, default): return True if not self.core.lgd.lock_installed(): logger.fatal('Failed to acquire installed data lock, only one instance of Legendary may ' 'install/import/move applications at a time.') @@ -429,7 +428,6 @@ class LegendaryCLI(LegendaryCLIReal): # Override logger for the local context to use message as part of the indirect return value logger = LgndrIndirectLogger(args.indirect_status, self.logger, logging.WARNING) get_boolean_choice = args.get_boolean_choice_handler - # def get_boolean_choice(x, default): return True # noinspection PyShadowingBuiltins def print(x): self.logger.info(x) if x else None diff --git a/rare/lgndr/glue/arguments.py b/rare/lgndr/glue/arguments.py index 36e1a46b..35f32cad 100644 --- a/rare/lgndr/glue/arguments.py +++ b/rare/lgndr/glue/arguments.py @@ -4,9 +4,10 @@ from typing import Callable, List, Optional, Dict from rare.lgndr.glue.monkeys import ( LgndrIndirectStatus, - GetBooleanChoiceProtocol, - get_boolean_choice, - verify_stdout, + get_boolean_choice_factory, + sdl_prompt_factory, + verify_stdout_factory, + ui_update_factory, DLManagerSignals, ) from rare.lgndr.models.downloading import UIUpdate @@ -33,7 +34,7 @@ class LgndrImportGameArgs: yes: bool = False # Rare: Extra arguments indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus) - get_boolean_choice: GetBooleanChoiceProtocol = get_boolean_choice + get_boolean_choice: Callable[[str, bool], bool] = get_boolean_choice_factory(True) @dataclass @@ -44,8 +45,8 @@ class LgndrUninstallGameArgs: yes: bool = False # Rare: Extra arguments indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus) - get_boolean_choice_main: GetBooleanChoiceProtocol = get_boolean_choice - get_boolean_choice_handler: GetBooleanChoiceProtocol = get_boolean_choice + get_boolean_choice_main: Callable[[str, bool], bool] = get_boolean_choice_factory(True) + get_boolean_choice_handler: Callable[[str, bool], bool] = get_boolean_choice_factory(True) @dataclass @@ -53,7 +54,7 @@ class LgndrVerifyGameArgs: app_name: str # Rare: Extra arguments indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus) - verify_stdout: Callable[[int, int, float, float], None] = verify_stdout + verify_stdout: Callable[[int, int, float, float], None] = verify_stdout_factory(None) @dataclass @@ -94,14 +95,11 @@ class LgndrInstallGameArgs: yes: bool = True # Rare: Extra arguments indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus) - get_boolean_choice: GetBooleanChoiceProtocol = get_boolean_choice - sdl_prompt: Callable[[str, str], List[str]] = lambda app_name, title: [""] - verify_stdout: Callable[[int, int, float, float], None] = verify_stdout + get_boolean_choice: Callable[[str, bool], bool] = get_boolean_choice_factory(True) + verify_stdout: Callable[[int, int, float, float], None] = verify_stdout_factory(None) - # def __post_init__(self): - # if self.sdl_prompt is None: - # self.sdl_prompt: Callable[[str, str], list] = \ - # lambda app_name, title: self.install_tag if self.install_tag is not None else [""] + def __post_init__(self): + self.sdl_prompt: Callable[[Dict, str], List[str]] = sdl_prompt_factory(self.install_tag) @dataclass @@ -119,8 +117,8 @@ class LgndrInstallGameRealArgs: # Rare: Extra arguments install_prereqs: bool = False indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus) - ui_update: Callable[[UIUpdate], None] = lambda ui: None - dlm_signals: DLManagerSignals = DLManagerSignals() + ui_update: Callable[[UIUpdate], None] = ui_update_factory(None) + dlm_signals: DLManagerSignals = field(default_factory=DLManagerSignals) @dataclass diff --git a/rare/lgndr/glue/monkeys.py b/rare/lgndr/glue/monkeys.py index b764b23d..bf784631 100644 --- a/rare/lgndr/glue/monkeys.py +++ b/rare/lgndr/glue/monkeys.py @@ -1,20 +1,48 @@ import logging from dataclasses import dataclass +from typing import Callable, List, Optional, Dict -from typing_extensions import Protocol +from rare.lgndr.models.downloading import UIUpdate + +logger = logging.getLogger("LgndrMonkeys") -class GetBooleanChoiceProtocol(Protocol): - def __call__(self, prompt: str, default: bool = ...) -> bool: - ... +def get_boolean_choice_factory(value: bool = True) -> Callable[[str, bool], bool]: + def get_boolean_choice(prompt: str, default: bool) -> bool: + logger.debug("get_boolean_choice: %s, default: %s, choice: %s", prompt, default, value) + return value + return get_boolean_choice -def get_boolean_choice(prompt: str, default: bool = True) -> bool: - return default +def sdl_prompt_factory(install_tag: Optional[List[str]] = None) -> Callable[[Dict, str], List[str]]: + def sdl_prompt(sdl_data: Dict, title: str) -> List[str]: + logger.debug("sdl_prompt: %s", title) + for key in sdl_data.keys(): + logger.debug("%s: %s %s", key, sdl_data[key]["tags"], sdl_data[key]["name"]) + tags = install_tag if install_tag is not None else [""] + logger.debug("choice: %s, tags: %s", install_tag, tags) + return tags + return sdl_prompt -def verify_stdout(a0: int, a1: int, a2: float, a3: float) -> None: - print(f"Verification progress: {a0}/{a1} ({a2:.01f}%) [{a3:.1f} MiB/s]\t\r") +def verify_stdout_factory( + callback: Callable[[int, int, float, float], None] = None +) -> Callable[[int, int, float, float], None]: + def verify_stdout(a0: int, a1: int, a2: float, a3: float) -> None: + if callback is not None and callable(callback): + callback(a0, a1, a2, a3) + else: + logger.info("Verification progress: %d/%d (%.01f%%) [%.1f MiB/s]", a0, a1, a2, a3) + return verify_stdout + + +def ui_update_factory(callback: Callable[[UIUpdate], None] = None) -> Callable[[UIUpdate], None]: + def ui_update(status: UIUpdate) -> None: + if callback is not None and callable(callback): + callback(status) + else: + logger.info("Installation progress: %s", status) + return ui_update class DLManagerSignals: diff --git a/rare/models/install.py b/rare/models/install.py index 9c16bcb2..efd076f9 100644 --- a/rare/models/install.py +++ b/rare/models/install.py @@ -1,8 +1,7 @@ import os -import platform as pf from dataclasses import dataclass from datetime import datetime, timedelta -from typing import List, Optional, Callable, Dict, Tuple +from typing import List, Optional, Dict, Tuple from legendary.models.downloading import AnalysisResult, ConditionCheckResult from legendary.models.game import Game, InstalledGame @@ -35,10 +34,6 @@ class InstallOptionsModel: silent: bool = False install_prereqs: bool = False - def __post_init__(self): - self.sdl_prompt: Callable[[str, str], list] = \ - lambda app_name, title: self.install_tag if self.install_tag is not None else [""] - def as_install_kwargs(self) -> Dict: return { k: getattr(self, k) diff --git a/requirements-flatpak.txt b/requirements-flatpak.txt new file mode 100644 index 00000000..86f81354 --- /dev/null +++ b/requirements-flatpak.txt @@ -0,0 +1,5 @@ +requests +QtAwesome +setuptools +legendary-gl>=0.20.34 +pypresence \ No newline at end of file diff --git a/requirements-full.txt b/requirements-full.txt index 5d94f533..93436a61 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -1,4 +1,3 @@ -typing_extensions requests PyQt5 QtAwesome diff --git a/requirements.txt b/requirements.txt index 55d56ef0..cf445de5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -typing_extensions requests PyQt5 QtAwesome diff --git a/setup.py b/setup.py index 77936e2b..bad9d7fa 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,6 @@ requirements = [ "PyQt5", "QtAwesome", 'pywin32; platform_system == "Windows"', - "typing_extensions" ] optional_reqs = dict(