From 5029921b09599dc4378d3c4d1b250a2c7d5631b8 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Wed, 7 Sep 2022 15:35:51 +0300 Subject: [PATCH 1/9] Move a bunch of class attributes to instance attributes --- rare/app.py | 2 +- rare/components/dialogs/install_dialog.py | 3 +++ rare/components/dialogs/launch_dialog.py | 2 +- rare/components/dialogs/login/__init__.py | 2 +- rare/components/tabs/games/__init__.py | 10 ++++------ rare/shared/image_manager.py | 21 ++++++++------------- 6 files changed, 18 insertions(+), 22 deletions(-) diff --git a/rare/app.py b/rare/app.py index efd10a64..8320c228 100644 --- a/rare/app.py +++ b/rare/app.py @@ -71,6 +71,7 @@ class App(RareApp): # set Application name for settings self.mainwindow: Optional[MainWindow] = None self.launch_dialog: Optional[LaunchDialog] = None + self.timer = QTimer() # launch app self.launch_dialog = LaunchDialog(parent=None) @@ -84,7 +85,6 @@ class App(RareApp): dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]) dt_now = datetime.utcnow() td = abs(dt_exp - dt_now) - self.timer = QTimer() self.timer.timeout.connect(self.re_login) self.timer.start(int(td.total_seconds() - 60) * 1000) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index ca58ecb6..2e288747 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -309,6 +309,9 @@ class InstallDialog(QDialog): self.show() def error_box(self, label: str = "", message: str = ""): + if message.startswith("403 Client Error: Forbidden for url:"): + message = self.tr("403 Client Error: Wait a few seconds and try Verify again") + self.options_changed = True self.ui.warn_label.setVisible(bool(label)) self.ui.warn_label.setText(label) self.ui.warn_message.setVisible(bool(message)) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 91c507aa..59e1a5d8 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -11,7 +11,7 @@ from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSi from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog from rare.utils.misc import CloudWorker -logger = getLogger("LoginDialog") +logger = getLogger("LaunchDialog") class LaunchWorker(QRunnable): diff --git a/rare/components/dialogs/login/__init__.py b/rare/components/dialogs/login/__init__.py index eb0399e6..66a1f346 100644 --- a/rare/components/dialogs/login/__init__.py +++ b/rare/components/dialogs/login/__init__.py @@ -12,7 +12,7 @@ from rare.widgets.sliding_stack import SlidingStackedWidget from .browser_login import BrowserLogin from .import_login import ImportLogin -logger = getLogger("Login") +logger = getLogger("LoginDialog") @dataclass diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index dfbafa2b..835c1289 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -1,16 +1,16 @@ from logging import getLogger -from typing import Tuple, Dict, Union, List +from typing import Tuple, Dict, Union, List, Set from PyQt5.QtCore import QSettings, Qt, pyqtSlot from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame from legendary.models.game import InstalledGame, Game -from rare.shared import ImageManagerSingleton from rare.shared import ( LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ApiResultsSingleton, + ImageManagerSingleton, ) from rare.widgets.library_layout import LibraryLayout from rare.widgets.sliding_stack import SlidingStackedWidget @@ -32,10 +32,6 @@ logger = getLogger("GamesTab") class GamesTab(QStackedWidget): - - updates = set() - active_filter = 0 - def __init__(self, parent=None): super(GamesTab, self).__init__(parent=parent) self.core = LegendaryCoreSingleton() @@ -47,6 +43,8 @@ class GamesTab(QStackedWidget): self.widgets: Dict[str, Tuple[ Union[InstalledIconWidget, UninstalledIconWidget], Union[InstalledListWidget, UninstalledListWidget]]] = {} + self.updates: Set = set() + self.active_filter: int = 0 self.uninstalled_games: List[Game] = [] self.game_list: List[Game] = self.api_results.game_list diff --git a/rare/shared/image_manager.py b/rare/shared/image_manager.py index a64cb62f..c36751b8 100644 --- a/rare/shared/image_manager.py +++ b/rare/shared/image_manager.py @@ -38,17 +38,13 @@ logger = getLogger("ImageManager") class ImageSize: class Preset: - __img_factor = 67 - __size: QSize - __divisor: float = 1.0 - __pixel_ratio: float = 1.0 - # lk: for prettier images set this to true - __smooth_transform: bool = False - def __init__(self, divisor: float, pixel_ratio: float): - self.__pixel_ratio = pixel_ratio + self.__img_factor = 67 self.__divisor = divisor + self.__pixel_ratio = pixel_ratio self.__size = QSize(self.__img_factor * 3, self.__img_factor * 4) * pixel_ratio / divisor + # lk: for prettier images set this to true + self.__smooth_transform: bool = False if divisor > 2: self.__smooth_transform = False @@ -107,12 +103,11 @@ class ImageManager(QObject): logger.debug(f" Emitting singal for game {self.game.app_name} - {self.game.app_title}") self.signals.completed.emit(self.game.app_name) - # lk: the ordering in __img_types matters for the order of fallbacks - __img_types: List = ["DieselGameBoxTall", "Thumbnail", "DieselGameBoxLogo"] - __dl_retries = 1 - __worker_app_names: List[str] = list() - def __init__(self, signals: GlobalSignals, core: LegendaryCore): + # lk: the ordering in __img_types matters for the order of fallbacks + self.__img_types: Tuple = ("DieselGameBoxTall", "Thumbnail", "DieselGameBoxLogo") + self.__dl_retries = 1 + self.__worker_app_names: List[str] = [] super(QObject, self).__init__() self.signals = signals self.core = core From 6a2af0be7c95cbe0badca80d3d3900c2388eec55 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Wed, 7 Sep 2022 19:19:14 +0300 Subject: [PATCH 2/9] Quote executable path for Windows --- rare/utils/misc.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rare/utils/misc.py b/rare/utils/misc.py index a6826571..f3c6cf12 100644 --- a/rare/utils/misc.py +++ b/rare/utils/misc.py @@ -284,10 +284,9 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link= if not for_rare: arguments.extend(["launch", app_name]) - shortcut.Targetpath = executable - shortcut.Arguments = shlex.join(arguments) # Maybe there is a better solution, but windows does not accept single quotes (Windows is weird) - shortcut.Arguments = shortcut.Arguments.replace("'", '"') + shortcut.Targetpath = shlex.quote(executable).replace("'", '"') + shortcut.Arguments = shlex.join(arguments).replace("'", '"') if for_rare: shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation) From ea383578f9cd4111fe4a0e508e88e5b218dfa0c2 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Wed, 7 Sep 2022 19:22:50 +0300 Subject: [PATCH 3/9] workflows: Quote python version in pylint --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 45dcb680..89e72c2b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip From 4b5b458fdb18a6abff2875f6e89466a707fc0bb2 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Wed, 7 Sep 2022 19:33:01 +0300 Subject: [PATCH 4/9] workflows: explicitly install pypresence --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 89e72c2b..d1bb2d88 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,7 @@ jobs: python -m pip install --upgrade pip pip install pylint pip install -r requirements.txt + pip install pypresence - name: Analysing the code with pylint run: | pylint -E rare --disable=E0611,E1123,E1120 --ignore=ui,singleton.py --extension-pkg-whitelist=PyQt5 From 377fd486dc98ecb19420968fcaea3964862fbaed Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Wed, 7 Sep 2022 19:42:07 +0300 Subject: [PATCH 5/9] stupid quotes --- .github/workflows/tests.yml | 4 +++- rare/__main__.py | 3 +-- rare/utils/misc.py | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d1bb2d88..dde4f957 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,7 +77,6 @@ jobs: name: Rare.AppImage path: Rare.AppImage - cx_freeze: runs-on: "windows-latest" steps: @@ -91,6 +90,8 @@ jobs: run: pip3 install -r requirements.txt - name: cx_freeze run: pip3 install --upgrade cx_freeze wheel + - name: pypresence + run: pip3 install pypresence - name: Build run: python freeze.py bdist_msi @@ -98,6 +99,7 @@ jobs: with: name: Rare-Windows.msi path: dist/*.msi + mac_os: runs-on: macos-latest steps: diff --git a/rare/__main__.py b/rare/__main__.py index cba7a615..7d4f1748 100644 --- a/rare/__main__.py +++ b/rare/__main__.py @@ -1,3 +1,4 @@ +import multiprocessing import os import pathlib import sys @@ -6,8 +7,6 @@ from argparse import ArgumentParser def main(): # fix cx_freeze - import multiprocessing - multiprocessing.freeze_support() # insert legendary for installed via pip/setup.py submodule to path diff --git a/rare/utils/misc.py b/rare/utils/misc.py index f3c6cf12..be8ee9a5 100644 --- a/rare/utils/misc.py +++ b/rare/utils/misc.py @@ -165,7 +165,7 @@ def get_size(b: Union[int, float]) -> str: def get_rare_executable() -> List[str]: - # lk: detech if nuitka + # lk: detect if nuitka if "__compiled__" in globals(): executable = [sys.executable] elif platform.system() == "Linux" or platform.system() == "Darwin": @@ -285,7 +285,10 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link= arguments.extend(["launch", app_name]) # Maybe there is a better solution, but windows does not accept single quotes (Windows is weird) + logger.warning(executable) + logger.warning(shlex.quote(executable).replace("'", '"')) shortcut.Targetpath = shlex.quote(executable).replace("'", '"') + logger.warning(shlex.join(arguments).replace("'", '"')) shortcut.Arguments = shlex.join(arguments).replace("'", '"') if for_rare: shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation) From d5d795ce794548eab10cfb6917eaa4c8ebe951b4 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Thu, 8 Sep 2022 01:27:37 +0300 Subject: [PATCH 6/9] Paths: Query paths after the `OrganizationName` and `ApplicationName` have been set At the point they were evaluated, `OrganizationName` and `ApplicationName` are unset resulting in wrong paths. As a quick fix, explicitly set them to their later values Per OS examples: Windows: before: data: C:\Users\\AppData\Local cache: C:\Users\\AppData\Local\cache after: data: C:\Users\\AppData\Local\Rare\Rare cache: C:\Users\\AppData\Local\Rare\Rare\cache --- rare/__main__.py | 4 +- rare/app.py | 71 +++++++++++----------- rare/components/main_window.py | 4 +- rare/components/tabs/downloads/__init__.py | 4 +- rare/components/tabs/settings/rare.py | 16 +++-- rare/components/tabs/shop/__init__.py | 2 +- rare/game_launch_helper/__init__.py | 8 +-- rare/lgndr/cli.py | 2 +- rare/shared/image_manager.py | 2 +- rare/utils/extra_widgets.py | 2 +- rare/utils/meta.py | 4 +- rare/utils/misc.py | 2 +- rare/utils/paths.py | 53 +++++++++++++--- rare/utils/steam_grades.py | 15 +++-- rare/widgets/rare_app.py | 6 +- 15 files changed, 117 insertions(+), 78 deletions(-) diff --git a/rare/__main__.py b/rare/__main__.py index 7d4f1748..84e1fc87 100644 --- a/rare/__main__.py +++ b/rare/__main__.py @@ -94,9 +94,9 @@ def main(): me = singleton.SingleInstance() except singleton.SingleInstanceException: print("Rare is already running") - from rare.utils.paths import data_dir + from rare.utils.paths import lock_file - with open(os.path.join(data_dir, "lockfile"), "w") as file: + with open(lock_file(), "w") as file: file.write("show") file.close() return diff --git a/rare/app.py b/rare/app.py index 8320c228..d423e848 100644 --- a/rare/app.py +++ b/rare/app.py @@ -24,14 +24,9 @@ from rare.shared import ( ArgumentsSingleton, ) from rare.shared.rare_core import RareCore -from rare.utils import legendary_utils, config_helper -from rare.utils.paths import cache_dir, tmp_dir +from rare.utils import legendary_utils, config_helper, paths from rare.widgets.rare_app import RareApp -start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute -file_name = os.path.join(cache_dir, "logs", f"Rare_{start_time}.log") -if not os.path.exists(os.path.dirname(file_name)): - os.makedirs(os.path.dirname(file_name)) logger = logging.getLogger("Rare") @@ -57,7 +52,39 @@ def excepthook(exc_type, exc_value, exc_tb): class App(RareApp): def __init__(self, args: Namespace): - super(App, self).__init__() + super(App, self).__init__(args) + + paths.create_dirs() + start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute + file_name = os.path.join(paths.log_dir(), f"Rare_{start_time}.log") + + # configure logging + if args.debug: + logging.basicConfig( + format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG + ) + logging.getLogger().setLevel(level=logging.DEBUG) + # keep requests, asyncio and pillow quiet + logging.getLogger("requests").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) + logging.getLogger("asyncio").setLevel(logging.WARNING) + logger.info( + f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n" + f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n" + f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n" + f" - Running {sys.executable} {' '.join(sys.argv)}\n" + f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}" + ) + else: + print(file_name) + logging.basicConfig( + format="[%(name)s] %(levelname)s: %(message)s", + level=logging.INFO, + filename=file_name, + ) + logger.info(f"Launching Rare version {rare.__version__}") + logger.info(f"Operating System: {platform.system()}") + self.rare_core = RareCore(args=args) self.args = ArgumentsSingleton() self.signals = GlobalSignalsSingleton() @@ -142,8 +169,8 @@ class App(RareApp): self.rare_core.deleteLater() del self.rare_core self.processEvents() - shutil.rmtree(tmp_dir) - os.makedirs(tmp_dir) + shutil.rmtree(paths.tmp_dir()) + os.makedirs(paths.tmp_dir()) self.exit(exit_code) @@ -152,32 +179,6 @@ def start(args): # set excepthook to show dialog with exception sys.excepthook = excepthook - # configure logging - if args.debug: - logging.basicConfig( - format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG - ) - logging.getLogger().setLevel(level=logging.DEBUG) - # keep requests, asyncio and pillow quiet - logging.getLogger("requests").setLevel(logging.WARNING) - logging.getLogger("urllib3").setLevel(logging.WARNING) - logging.getLogger("asyncio").setLevel(logging.WARNING) - logger.info( - f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n" - f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n" - f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n" - f" - Running {sys.executable} {' '.join(sys.argv)}\n" - f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}" - ) - else: - logging.basicConfig( - format="[%(name)s] %(levelname)s: %(message)s", - level=logging.INFO, - filename=file_name, - ) - logger.info(f"Launching Rare version {rare.__version__}") - logger.info(f"Operating System: {platform.system()}") - while True: app = App(args) exit_code = app.exec_() diff --git a/rare/components/main_window.py b/rare/components/main_window.py index b5e50942..60759be6 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -8,7 +8,7 @@ from PyQt5.QtWidgets import QMainWindow, QApplication, QStatusBar, QScrollArea, from rare.components.tabs import TabWidget from rare.components.tray_icon import TrayIcon from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton -from rare.utils.paths import data_dir +from rare.utils.paths import lock_file logger = getLogger("MainWindow") @@ -125,7 +125,7 @@ class MainWindow(QMainWindow): self.hide() def timer_finished(self): - file_path = os.path.join(data_dir, "lockfile") + file_path = lock_file() if os.path.exists(file_path): file = open(file_path, "r") action = file.read() diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 26739a26..11400817 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -1,6 +1,6 @@ import datetime from logging import getLogger -from typing import List, Dict, Union +from typing import List, Dict, Union, Set from PyQt5.QtCore import QThread, pyqtSignal, QSettings, pyqtSlot from PyQt5.QtWidgets import ( @@ -31,7 +31,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): dl_queue: List[InstallQueueItemModel] = [] dl_status = pyqtSignal(int) - def __init__(self, updates: list): + def __init__(self, updates: Set): super(DownloadsTab, self).__init__() self.setupUi(self) self.core = LegendaryCoreSingleton() diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py index 48bdc573..c7a5fd1b 100644 --- a/rare/components/tabs/settings/rare.py +++ b/rare/components/tabs/settings/rare.py @@ -10,7 +10,7 @@ from PyQt5.QtWidgets import QWidget, QMessageBox from rare.shared import LegendaryCoreSingleton from rare.components.tabs.settings.widgets.rpc import RPCSettings from rare.ui.components.tabs.settings.rare import Ui_RareSettings -from rare.utils.paths import cache_dir +from rare.utils.paths import log_dir from rare.utils.misc import ( get_translations, get_color_schemes, @@ -51,7 +51,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.settings = QSettings() language = self.settings.value("language", self.core.language_code, type=str) - self.logdir = os.path.join(cache_dir, "logs") # Select lang self.lang_select.addItems([i[1] for i in languages]) @@ -143,19 +142,18 @@ class RareSettings(QWidget, Ui_RareSettings): self.log_dir_open_button.clicked.connect(self.open_dir) self.log_dir_clean_button.clicked.connect(self.clean_logdir) - logdir = os.path.join(cache_dir, "logs") # get size of logdir size = 0 - for i in os.listdir(logdir): - size += os.path.getsize(os.path.join(logdir, i)) + for i in os.listdir(log_dir()): + size += os.path.getsize(os.path.join(log_dir(), i)) self.log_dir_size_label.setText(get_size(size)) # self.log_dir_clean_button.setVisible(False) # self.log_dir_size_label.setVisible(False) def clean_logdir(self): - for i in os.listdir(os.path.join(cache_dir, "logs")): - os.remove(os.path.join(cache_dir, f"logs/{i}")) + for i in os.listdir(log_dir()): + os.remove(os.path.join(log_dir(), f"{i}")) self.log_dir_size_label.setText("0KB") def create_start_menu_link(self): @@ -216,10 +214,10 @@ class RareSettings(QWidget, Ui_RareSettings): def open_dir(self): if platform.system() == "Windows": - os.startfile(self.logdir) # pylint: disable=E1101 + os.startfile(log_dir()) # pylint: disable=E1101 else: opener = "open" if sys.platform == "darwin" else "xdg-open" - subprocess.Popen([opener, self.logdir]) + subprocess.Popen([opener, log_dir()]) def save_window_size(self): self.settings.setValue("save_size", self.save_size.isChecked()) diff --git a/rare/components/tabs/shop/__init__.py b/rare/components/tabs/shop/__init__.py index 53be94ba..543c754b 100644 --- a/rare/components/tabs/shop/__init__.py +++ b/rare/components/tabs/shop/__init__.py @@ -23,7 +23,7 @@ class Shop(QStackedWidget): self.core.country_code, ) - self.shop = ShopWidget(cache_dir, self.core, self.api_core) + self.shop = ShopWidget(cache_dir(), self.core, self.api_core) self.wishlist_widget = Wishlist(self.api_core) self.store_tabs = QTabWidget() diff --git a/rare/game_launch_helper/__init__.py b/rare/game_launch_helper/__init__.py index 35a13e3f..5dbabefc 100644 --- a/rare/game_launch_helper/__init__.py +++ b/rare/game_launch_helper/__init__.py @@ -64,10 +64,10 @@ class GameProcessApp(RareApp): console: Console = None success: bool = True - def __init__(self, app_name: str): - super(GameProcessApp, self).__init__() + def __init__(self, args: Namespace): + super(GameProcessApp, self).__init__(args) self.game_process = QProcess() - self.app_name = app_name + self.app_name = args.app_name self.logger = getLogger(self.app_name) self.core = LegendaryCore() @@ -211,7 +211,7 @@ def start_game(args: Namespace): level=logging.INFO, ) - app = GameProcessApp(args.app_name) + app = GameProcessApp(args) app.setQuitOnLastWindowClosed(True) def excepthook(exc_type, exc_value, exc_tb): diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py index 667b19a3..45ad327d 100644 --- a/rare/lgndr/cli.py +++ b/rare/lgndr/cli.py @@ -30,7 +30,7 @@ class LegendaryCLI(LegendaryCLIReal): # noinspection PyMissingConstructor def __init__(self, core: LegendaryCore): self.core = core - self.logger = logging.getLogger('cli') + self.logger = logging.getLogger('Api') self.logging_queue = None self.ql = self.setup_threaded_logging() diff --git a/rare/shared/image_manager.py b/rare/shared/image_manager.py index c36751b8..1412a96e 100644 --- a/rare/shared/image_manager.py +++ b/rare/shared/image_manager.py @@ -112,7 +112,7 @@ class ImageManager(QObject): self.signals = signals self.core = core - self.image_dir = Path(image_dir) + self.image_dir: Path = image_dir() if not self.image_dir.is_dir(): self.image_dir.mkdir() logger.info(f"Created image directory at {self.image_dir}") diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index a6ef5db2..0dc72160 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -418,7 +418,7 @@ class ImageLabel(QLabel): def __init__(self): super(ImageLabel, self).__init__() - self.path = tmp_dir + self.path = tmp_dir() self.manager = QtRequestManager("bytes") def update_image(self, url, name="", size: tuple = (240, 320)): diff --git a/rare/utils/meta.py b/rare/utils/meta.py index 3b120392..54431754 100644 --- a/rare/utils/meta.py +++ b/rare/utils/meta.py @@ -34,7 +34,7 @@ class RareGameMeta: def __init__(self): meta_data = {} - if os.path.exists(p := os.path.join(data_dir, "game_meta.json")): + if os.path.exists(p := os.path.join(data_dir(), "game_meta.json")): try: meta_data = json.load(open(p)) except json.JSONDecodeError: @@ -59,6 +59,6 @@ class RareGameMeta: def save_file(self): json.dump( {app_name: data.__dict__() for app_name, data in self._meta.items()}, - open(os.path.join(data_dir, "game_meta.json"), "w"), + open(os.path.join(data_dir(), "game_meta.json"), "w"), indent=4 ) diff --git a/rare/utils/misc.py b/rare/utils/misc.py index be8ee9a5..4599ba8d 100644 --- a/rare/utils/misc.py +++ b/rare/utils/misc.py @@ -195,7 +195,7 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link= if not for_rare: igame = core.get_installed_game(app_name) - icon = os.path.join(os.path.join(image_dir, igame.app_name, "installed.png")) + icon = os.path.join(os.path.join(image_dir(), igame.app_name, "installed.png")) icon = icon.replace(".png", "") if platform.system() == "Linux": diff --git a/rare/utils/paths.py b/rare/utils/paths.py index ef9284d0..8f3a162d 100644 --- a/rare/utils/paths.py +++ b/rare/utils/paths.py @@ -1,13 +1,52 @@ +import os +import shutil from pathlib import Path from PyQt5.QtCore import QStandardPaths resources_path = Path(__file__).absolute().parent.parent.joinpath("resources") -data_dir = Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare") -cache_dir = Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare") -image_dir = data_dir.joinpath("images") -tmp_dir = cache_dir.joinpath("tmp") -for path in (data_dir, cache_dir, image_dir, tmp_dir): - if not path.exists(): - path.mkdir(parents=True) +# lk: delete old Rare directories +for old_dir in [ + Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare").joinpath("tmp"), + Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare").joinpath("images"), + Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare"), + Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare"), +]: + if old_dir.exists(): + # lk: case-sensitive matching on Winblows + if old_dir.stem in os.listdir(old_dir.parent): + print(old_dir) + shutil.rmtree(old_dir, ignore_errors=True) + + +# lk: TempLocation doesn't depend on OrganizationName or ApplicationName +# lk: so it is fine to use it before initializing the QApplication +def lock_file() -> Path: + return Path(QStandardPaths.writableLocation(QStandardPaths.TempLocation), "Rare.lock") + + +def data_dir() -> Path: + return Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation)) + + +def cache_dir() -> Path: + return Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation)) + + +def image_dir() -> Path: + return data_dir().joinpath("images") + + +def log_dir() -> Path: + return cache_dir().joinpath("logs") + + +def tmp_dir() -> Path: + return cache_dir().joinpath("tmp") + + +def create_dirs() -> None: + for path in (data_dir(), cache_dir(), image_dir(), log_dir(), tmp_dir()): + if not path.exists(): + path.mkdir(parents=True) diff --git a/rare/utils/steam_grades.py b/rare/utils/steam_grades.py index 3403d40c..172e233e 100644 --- a/rare/utils/steam_grades.py +++ b/rare/utils/steam_grades.py @@ -6,13 +6,11 @@ from datetime import date import requests from PyQt5.QtCore import pyqtSignal, QRunnable, QObject, QCoreApplication -from legendary.core import LegendaryCore +from rare.lgndr.core import LegendaryCore from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton from rare.utils.paths import data_dir, cache_dir replace_chars = ",;.:-_ " - -file = os.path.join(cache_dir, "game_list.json") url = "https://api.steampowered.com/ISteamApps/GetAppList/v2/" @@ -20,7 +18,7 @@ class SteamWorker(QRunnable): class Signals(QObject): rating_signal = pyqtSignal(str) - app_name:str = "" + app_name: str = "" def __init__(self, core: LegendaryCore): super(SteamWorker, self).__init__() @@ -55,7 +53,7 @@ def get_rating(app_name: str): args = ArgumentsSingleton() global __grades_json if __grades_json is None: - if os.path.exists(p := os.path.join(data_dir, "steam_ids.json")): + if os.path.exists(p := os.path.join(data_dir(), "steam_ids.json")): grades = json.loads(open(p).read()) __grades_json = grades else: @@ -72,7 +70,7 @@ def get_rating(app_name: str): steam_id = get_steam_id(game.app_title) grade = get_grade(steam_id) grades[app_name] = {"steam_id": steam_id, "grade": grade} - with open(os.path.join(data_dir, "steam_ids.json"), "w") as f: + with open(os.path.join(data_dir(), "steam_ids.json"), "w") as f: f.write(json.dumps(grades)) f.close() return grade @@ -96,8 +94,8 @@ def get_grade(steam_code): def load_json() -> dict: + file = os.path.join(cache_dir(), "game_list.json") if not os.path.exists(file): - response = requests.get(url) steam_ids = json.loads(response.text)["applist"]["apps"] ids = {} @@ -113,6 +111,7 @@ def load_json() -> dict: def get_steam_id(title: str): + file = os.path.join(cache_dir(), "game_list.json") # workarounds for satisfactory title = title.replace("Early Access", "").replace("Experimental", "").strip() global __steam_ids_json @@ -146,7 +145,7 @@ def get_steam_id(title: str): def check_time(): # this function check if it's time to update - global file + file = os.path.join(cache_dir(), "game_list.json") json_table = json.loads(open(file, "r").read()) today = date.today() diff --git a/rare/widgets/rare_app.py b/rare/widgets/rare_app.py index 1b03cabc..53f39754 100644 --- a/rare/widgets/rare_app.py +++ b/rare/widgets/rare_app.py @@ -1,8 +1,10 @@ import os import sys +from argparse import Namespace from logging import getLogger +from pathlib import Path -from PyQt5.QtCore import Qt, QSettings, QTranslator +from PyQt5.QtCore import Qt, QSettings, QTranslator, QStandardPaths from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication @@ -17,7 +19,7 @@ from rare.utils.misc import set_color_pallete, set_style_sheet class RareApp(QApplication): logger = getLogger("RareApp") - def __init__(self): + def __init__(self, args: Namespace): super(RareApp, self).__init__(sys.argv) self.setQuitOnLastWindowClosed(False) if hasattr(Qt, "AA_UseHighDpiPixmaps"): From 7ebeee0d1e0d0df829cc28474c587431857f0407 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Thu, 8 Sep 2022 02:13:10 +0300 Subject: [PATCH 7/9] App: Log both to file and to stderr --- rare/app.py | 16 +++++++++++++--- rare/utils/paths.py | 1 - rare/widgets/rare_app.py | 10 +++++----- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/rare/app.py b/rare/app.py index d423e848..4c2887a1 100644 --- a/rare/app.py +++ b/rare/app.py @@ -54,15 +54,24 @@ class App(RareApp): def __init__(self, args: Namespace): super(App, self).__init__(args) - paths.create_dirs() start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute file_name = os.path.join(paths.log_dir(), f"Rare_{start_time}.log") + for handler in logging.root.handlers[:]: + logging.root.removeHandler(handler) + + stream_handler = logging.StreamHandler(sys.stderr) + stream_handler.setFormatter(fmt=logging.Formatter("[%(name)s] %(levelname)s: %(message)s")) + # configure logging if args.debug: logging.basicConfig( - format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG + format="[%(name)s] %(levelname)s: %(message)s", + level=logging.DEBUG, + filename=file_name, ) + stream_handler.setLevel(logging.DEBUG) + logging.root.addHandler(stream_handler) logging.getLogger().setLevel(level=logging.DEBUG) # keep requests, asyncio and pillow quiet logging.getLogger("requests").setLevel(logging.WARNING) @@ -76,12 +85,13 @@ class App(RareApp): f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}" ) else: - print(file_name) logging.basicConfig( format="[%(name)s] %(levelname)s: %(message)s", level=logging.INFO, filename=file_name, ) + stream_handler.setLevel(logging.INFO) + logging.root.addHandler(stream_handler) logger.info(f"Launching Rare version {rare.__version__}") logger.info(f"Operating System: {platform.system()}") diff --git a/rare/utils/paths.py b/rare/utils/paths.py index 8f3a162d..ba342ec7 100644 --- a/rare/utils/paths.py +++ b/rare/utils/paths.py @@ -16,7 +16,6 @@ for old_dir in [ if old_dir.exists(): # lk: case-sensitive matching on Winblows if old_dir.stem in os.listdir(old_dir.parent): - print(old_dir) shutil.rmtree(old_dir, ignore_errors=True) diff --git a/rare/widgets/rare_app.py b/rare/widgets/rare_app.py index 53f39754..7d1bed82 100644 --- a/rare/widgets/rare_app.py +++ b/rare/widgets/rare_app.py @@ -2,9 +2,8 @@ import os import sys from argparse import Namespace from logging import getLogger -from pathlib import Path -from PyQt5.QtCore import Qt, QSettings, QTranslator, QStandardPaths +from PyQt5.QtCore import Qt, QSettings, QTranslator from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication @@ -12,7 +11,7 @@ from PyQt5.QtWidgets import QApplication from legendary.core import LegendaryCore import rare.resources.resources -from rare.utils.paths import resources_path +from rare.utils import paths from rare.utils.misc import set_color_pallete, set_style_sheet @@ -27,6 +26,7 @@ class RareApp(QApplication): self.setApplicationName("Rare") self.setOrganizationName("Rare") + paths.create_dirs() self.settings = QSettings() # Translator @@ -53,7 +53,7 @@ class RareApp(QApplication): self.setWindowIcon(QIcon(":/images/Rare.png")) def load_translator(self, lang: str): - if os.path.isfile(f := os.path.join(resources_path, "languages", f"{lang}.qm")): + if os.path.isfile(f := os.path.join(paths.resources_path, "languages", f"{lang}.qm")): self.translator.load(f) self.logger.info(f"Your language is supported: {lang}") elif not lang == "en": @@ -61,7 +61,7 @@ class RareApp(QApplication): self.installTranslator(self.translator) # translator for qt stuff - if os.path.isfile(f := os.path.join(resources_path, f"qt_{lang}.qm")): + if os.path.isfile(f := os.path.join(paths.resources_path, f"qt_{lang}.qm")): self.qt_translator = QTranslator() self.qt_translator.load(f) self.installTranslator(self.qt_translator) From c6b9f5c64f5a50004042f12070c273e9c7e72228 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Thu, 8 Sep 2022 13:16:15 +0300 Subject: [PATCH 8/9] Fix shortcut creation on windows --- rare/app.py | 16 ++++++++-------- rare/utils/misc.py | 19 +++++++------------ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/rare/app.py b/rare/app.py index 4c2887a1..ef48f817 100644 --- a/rare/app.py +++ b/rare/app.py @@ -60,18 +60,18 @@ class App(RareApp): for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) - stream_handler = logging.StreamHandler(sys.stderr) - stream_handler.setFormatter(fmt=logging.Formatter("[%(name)s] %(levelname)s: %(message)s")) + file_handler = logging.FileHandler(filename=file_name, encoding="utf-8") + file_handler.setFormatter(fmt=logging.Formatter("[%(name)s] %(levelname)s: %(message)s")) # configure logging if args.debug: logging.basicConfig( format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG, - filename=file_name, + stream=sys.stderr, ) - stream_handler.setLevel(logging.DEBUG) - logging.root.addHandler(stream_handler) + file_handler.setLevel(logging.DEBUG) + logging.root.addHandler(file_handler) logging.getLogger().setLevel(level=logging.DEBUG) # keep requests, asyncio and pillow quiet logging.getLogger("requests").setLevel(logging.WARNING) @@ -88,10 +88,10 @@ class App(RareApp): logging.basicConfig( format="[%(name)s] %(levelname)s: %(message)s", level=logging.INFO, - filename=file_name, + stream=sys.stderr, ) - stream_handler.setLevel(logging.INFO) - logging.root.addHandler(stream_handler) + file_handler.setLevel(logging.INFO) + logging.root.addHandler(file_handler) logger.info(f"Launching Rare version {rare.__version__}") logger.info(f"Operating System: {platform.system()}") diff --git a/rare/utils/misc.py b/rare/utils/misc.py index 4599ba8d..15c563be 100644 --- a/rare/utils/misc.py +++ b/rare/utils/misc.py @@ -180,10 +180,12 @@ def get_rare_executable() -> List[str]: elif platform.system() == "Windows": executable = [sys.executable] - if not sys.executable.endswith("Rare.exe"): + if sys.executable != os.path.abspath(sys.argv[0]): + executable.append(os.path.abspath(sys.argv[0])) + + if executable[0].endswith("python.exe"): # be sure to start consoleless then executable[0] = executable[0].replace("python.exe", "pythonw.exe") - executable.extend(["-m", "rare"]) else: executable = [sys.executable] @@ -208,6 +210,7 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link= if not os.path.exists(path): return False executable = get_rare_executable() + executable = shlex.join(executable) if for_rare: with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file: @@ -274,21 +277,13 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link= if len(executable) > 1: arguments.extend(executable[1:]) - executable = executable[0] - - if not sys.executable.endswith("Rare.exe"): - # be sure to start consoleless then - executable = sys.executable.replace("python.exe", "pythonw.exe") - arguments.append(os.path.abspath(sys.argv[0])) + executable = executable[0] if not for_rare: arguments.extend(["launch", app_name]) + shortcut.Targetpath = executable # Maybe there is a better solution, but windows does not accept single quotes (Windows is weird) - logger.warning(executable) - logger.warning(shlex.quote(executable).replace("'", '"')) - shortcut.Targetpath = shlex.quote(executable).replace("'", '"') - logger.warning(shlex.join(arguments).replace("'", '"')) shortcut.Arguments = shlex.join(arguments).replace("'", '"') if for_rare: shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation) From e4638c2fa38c16fb769974a826228f497026367a Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Thu, 8 Sep 2022 13:33:46 +0300 Subject: [PATCH 9/9] Console: Center window on show() --- rare/game_launch_helper/__init__.py | 2 +- rare/game_launch_helper/console.py | 33 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/rare/game_launch_helper/__init__.py b/rare/game_launch_helper/__init__.py index 5dbabefc..ac97e272 100644 --- a/rare/game_launch_helper/__init__.py +++ b/rare/game_launch_helper/__init__.py @@ -61,7 +61,7 @@ class GameProcessApp(RareApp): server: QLocalServer socket: Optional[QLocalSocket] = None exit_app = pyqtSignal() - console: Console = None + console: Optional[Console] = None success: bool = True def __init__(self, args: Namespace): diff --git a/rare/game_launch_helper/console.py b/rare/game_launch_helper/console.py index c0c93139..1046ff6c 100644 --- a/rare/game_launch_helper/console.py +++ b/rare/game_launch_helper/console.py @@ -1,7 +1,7 @@ import platform -from PyQt5.QtCore import QProcessEnvironment, pyqtSignal -from PyQt5.QtGui import QTextCursor, QFont +from PyQt5.QtCore import QProcessEnvironment, pyqtSignal, QSize +from PyQt5.QtGui import QTextCursor, QFont, QCursor from PyQt5.QtWidgets import ( QPlainTextEdit, QDialog, @@ -10,7 +10,7 @@ from PyQt5.QtWidgets import ( QVBoxLayout, QHBoxLayout, QSpacerItem, - QSizePolicy, QTableWidgetItem, QHeaderView, + QSizePolicy, QTableWidgetItem, QHeaderView, QApplication, ) from rare.ui.components.extra.console_env import Ui_ConsoleEnv @@ -24,7 +24,7 @@ class Console(QDialog): def __init__(self, parent=None): super(Console, self).__init__(parent=parent) self.setWindowTitle("Rare - Console") - self.setGeometry(0, 0, 600, 400) + self.setGeometry(0, 0, 640, 480) layout = QVBoxLayout() self.console = ConsoleEdit(self) @@ -63,6 +63,31 @@ class Console(QDialog): self.env_variables = ConsoleEnv(self) self.env_variables.hide() + def show(self) -> None: + super(Console, self).show() + self.center_window() + + def center_window(self): + # get the margins of the decorated window + margins = self.windowHandle().frameMargins() + # get the screen the cursor is on + current_screen = QApplication.screenAt(QCursor.pos()) + if not current_screen: + current_screen = QApplication.primaryScreen() + # get the available screen geometry (excludes panels/docks) + screen_rect = current_screen.availableGeometry() + decor_width = margins.left() + margins.right() + decor_height = margins.top() + margins.bottom() + window_size = QSize(self.width(), self.height()).boundedTo( + screen_rect.size() - QSize(decor_width, decor_height) + ) + + self.resize(window_size) + self.move( + screen_rect.center() + - self.rect().adjusted(0, 0, decor_width, decor_height).center() + ) + def save(self): file, ok = QFileDialog.getSaveFileName( self, "Save output", "", "Log Files (*.log);;All Files (*)"