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(