1
0
Fork 0
mirror of synced 2024-06-26 10:11:19 +12:00

Merge pull request #242 from loathingKernel/fixups

Nuitka and another round of fixes for Windows
This commit is contained in:
Dummerle 2022-09-11 23:39:58 +02:00 committed by GitHub
commit 4ef755fa2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 398 additions and 144 deletions

44
.github/workflows/checks.yml vendored Normal file
View file

@ -0,0 +1,44 @@
name: "Checks"
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'rare/**'
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- main
paths:
- 'rare/**'
jobs:
pylint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install Test Dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
- name: Install Target Dependencies
run: |
pip3 install -r requirements.txt
pip3 install -r requirements-presence.txt
- name: Analysis with pylint
run: |
pylint -E rare --disable=E0611,E1123,E1120 --ignore=ui,singleton.py --extension-pkg-whitelist=PyQt5

View file

@ -1,30 +1,12 @@
name: "Test"
name: "Release Tests"
on:
workflow_dispatch:
jobs:
pylint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install dependencies
run: |
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
jobs:
deb-package:
runs-on: ubuntu-latest
steps:
@ -46,7 +28,8 @@ jobs:
makedeb -d
mv *.deb Rare.deb
- uses: actions/upload-artifact@v2
- name: Upload to Artifacts
uses: actions/upload-artifact@v2
with:
name: Rare.deb
path: build/Rare.deb
@ -72,11 +55,55 @@ jobs:
appimage-builder --skip-test
mv Rare-*.AppImage Rare.AppImage
- uses: actions/upload-artifact@v2
- name: Upload to Artifacts
uses: actions/upload-artifact@v2
with:
name: Rare.AppImage
path: Rare.AppImage
nuitka:
runs-on: "windows-latest"
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install build dependencies
run: pip3 install nuitka ordered-set
- name: Install target dependencies
run: |
pip3 install -r requirements.txt
pip3 install -r requirements-presence.txt
- name: Version
id: version
run: |
git fetch --prune --unshallow
echo "::set-output name=tag_offset::$(git describe --long --tags)"
echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Build
run: >-
python -m nuitka
--assume-yes-for-downloads
--follow-imports --prefer-source-code --mingw64 --lto=no --jobs=2 --static-libpython=no --standalone
--enable-plugin=anti-bloat --enable-plugin=pyqt5 --show-anti-bloat-changes --nofollow-import-to="*.tests"
--nofollow-import-to="*.distutils" --include-package-data=qtawesome
--include-data-dir=rare\resources\images=rare\resources\images
--include-data-files=rare\resources\languages=rare\resources\languages="*.qm"
--windows-icon-from-ico=rare\resources\images\Rare.ico
--windows-company-name=Rare --windows-product-name=Rare --windows-file-description=rare.exe
--windows-file-version=0.0.0.0
--windows-product-version=0.0.0.0
--windows-disable-console
rare
- name: Upload to Artifacts
uses: actions/upload-artifact@v2
with:
name: Rare-Windows-${{ steps.version.outputs.tag_offset }}
path: rare.dist
cx_freeze:
runs-on: "windows-latest"
steps:
@ -85,19 +112,28 @@ jobs:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Dependencies
run: pip3 install -r requirements.txt
- name: cx_freeze
python-version: '3.9'
- name: Install Build Dependencies
run: pip3 install --upgrade cx_freeze wheel
- name: pypresence
run: pip3 install pypresence
- name: Install Target Dependencies
run: |
pip3 install -r requirements.txt
pip3 install -r requirements-presence.txt
- name: Version
id: version
run: |
git fetch --prune --unshallow
echo "::set-output name=tag_offset::$(git describe --long --tags)"
echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Build
run: python freeze.py bdist_msi
run: |
python freeze.py bdist_msi
mv dist/*.msi dist/Rare-Windows.msi
- uses: actions/upload-artifact@v2
- name: Upload to Artifacts
uses: actions/upload-artifact@v2
with:
name: Rare-Windows.msi
name: Rare-Windows-${{ steps.version.outputs.tag_offset }}.msi
path: dist/*.msi
mac_os:
@ -120,8 +156,12 @@ jobs:
run: mv rare/__main__.py __main__.py
- name: run pyinstaller
run: |
pyinstaller -F --name Rare --add-data "rare/resources/languages/*:rare/resources/languages" --add-data "rare/resources/images/*:rare/resources/images/" --windowed --icon rare/resources/images/Rare.icns --hidden-import=legendary __main__.py
run: >-
pyinstaller -F --name Rare
--add-data "rare/resources/languages/*:rare/resources/languages"
--add-data "rare/resources/images/*:rare/resources/images/"
--windowed --icon rare/resources/images/Rare.icns
--hidden-import=legendary __main__.py
- name: create dmg
run: |

View file

@ -1,9 +1,12 @@
name: New Release
name: "Release"
on:
release:
types: [ published ]
jobs:
pypi-deploy:
if: "!github.event.release.prerelease"
@ -48,7 +51,7 @@ jobs:
makedeb -d
mv *.deb Rare.deb
- name: Upload files to GitHub
- name: Upload to Releases
uses: svenstaro/upload-release-action@2.2.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
@ -79,7 +82,7 @@ jobs:
mv Rare-*.AppImage Rare.AppImage
mv Rare-*.AppImage.zsync Rare.AppImage.zsync
- name: Upload AppImage to GitHub
- name: Upload to Releases
uses: svenstaro/upload-release-action@2.2.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
@ -96,6 +99,49 @@ jobs:
tag: ${{ github.ref }}
overwrite: true
nuitka:
runs-on: "windows-latest"
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install build dependencies
run: pip3 install nuitka ordered-set
- name: Install target dependencies
run: |
pip3 install -r requirements.txt
pip3 install -r requirements-presence.txt
- name: Build
run: >-
python -m nuitka
--assume-yes-for-downloads
--follow-imports --prefer-source-code --mingw64 --lto=no --jobs=2 --static-libpython=no --standalone
--enable-plugin=anti-bloat --enable-plugin=pyqt5 --show-anti-bloat-changes --nofollow-import-to="*.tests"
--nofollow-import-to="*.distutils" --include-package-data=qtawesome
--include-data-dir=rare\resources\images=rare\resources\images
--include-data-files=rare\resources\languages=rare\resources\languages="*.qm"
--windows-icon-from-ico=rare\resources\images\Rare.ico
--windows-company-name=Rare --windows-product-name=Rare --windows-file-description=rare.exe
--windows-file-version=${{ github.event.release.tag_name }}
--windows-product-version=${{ github.event.release.tag_name }}
--windows-disable-console
rare
- name: Compress
run: |
python -c "import shutil; shutil.make_archive('Rare-Windows', 'zip', 'rare.dist')"
- name: Upload to Releases
uses: svenstaro/upload-release-action@2.2.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: Rare-Windows.zip
asset_name: Rare-Windows-${{ github.event.release.tag_name }}.zip
tag: ${{ github.ref }}
overwrite: true
cx_freeze:
runs-on: "windows-latest"
steps:
@ -104,24 +150,27 @@ jobs:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Dependencies
run: pip3 install -r requirements.txt
- name: cx_freeze
python-version: '3.9'
- name: Install Build Dependencies
run: pip3 install --upgrade cx_freeze wheel
- name: Install Target Dependencies
run: |
pip3 install -r requirements.txt
pip3 install -r requirements-presence.txt
- name: Build
run: python freeze.py bdist_msi
run: |
python freeze.py bdist_msi
mv dist/*.msi dist/Rare-Windows.msi
- name: Rename File
run: mv dist/*.msi dist/Rare.msi
- name: Upload to GitHub
- name: Upload to Releases
uses: svenstaro/upload-release-action@2.2.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: dist/Rare.msi
asset_name: Rare-${{ github.event.release.tag_name }}.msi
file: dist/Rare-Windows.msi
asset_name: Rare-Windows-${{ github.event.release.tag_name }}.msi
tag: ${{ github.ref }}
overwrite: true
mac_os:
runs-on: macos-latest
steps:
@ -142,8 +191,12 @@ jobs:
run: mv rare/__main__.py __main__.py
- name: run pyinstaller
run: |
pyinstaller -F --name Rare --add-data "rare/resources/languages/*:rare/resources/languages" --add-data "rare/resources/images/*:rare/resources/images/" --windowed --icon rare/resources/images/Rare.icns --hidden-import=legendary __main__.py
run: >-
pyinstaller -F --name Rare
--add-data "rare/resources/languages/*:rare/resources/languages"
--add-data "rare/resources/images/*:rare/resources/images/"
--windowed --icon rare/resources/images/Rare.icns
--hidden-import=legendary __main__.py
- name: create dmg
run: |

1
misc/nuitka_rare.bat Normal file
View file

@ -0,0 +1 @@
python -m nuitka --assume-yes-for-downloads --follow-imports --prefer-source-code --mingw64 --lto=no --jobs=2 --static-libpython=no --standalone --enable-plugin=anti-bloat --enable-plugin=pyqt5 --show-anti-bloat-changes --nofollow-import-to="*.tests" --nofollow-import-to="*.distutils" --include-package-data=qtawesome --include-data-dir=rare\resources\images=rare\resources\images --include-data-files=rare\resources\languages=rare\resources\languages="*.qm" --windows-icon-from-ico=rare\resources\images\Rare.ico --windows-company-name=Rare --windows-product-name=Rare --windows-file-description=rare.exe --windows-file-version=1.9.0 --windows-product-version=1.9.0 --windows-disable-console rare

5
misc/pip_upgrade_venv.py Normal file
View file

@ -0,0 +1,5 @@
import pkg_resources
from subprocess import call
for dist in pkg_resources.working_set:
call("python -m pip install --upgrade " + dist.project_name, shell=True)

View file

@ -33,24 +33,36 @@ pywebview = [
legendary-gl = "^0.20.28"
typing-extensions = "^4.3.0"
[tool.poetry.scripts]
start = "rare.__main__:main"
[tool.poetry.dev-dependencies]
Nuitka = "^1.0.6"
pylint = "^2.15.0"
black = "^22.6.0"
#[build-system]
#requires = ["setuptools>=42"z, "wheel", "nuitka", "toml"]
#requires = ["setuptools>=42", "wheel", "nuitka", "toml"]
#build-backend = "nuitka.distutils.Build"
[nuitka]
show-scons = true
enable-plugin = ["pyqt5", "anti-bloat"]
assume-yes-for-downloads = true
follow-imports = true
prefer-source-code = true
mingw64 = true
lto = true
static-libpython = false
standalone = true
show-scons = false
enable-plugin = ["anti-bloat", "pyqt5"]
show-anti-bloat-changes = true
nofollow-import-to = ["*.tests", "*.distutils"]
include-package-data = "qtawesome"
include-data-dir = "rare/resources/images=rare/resources/images"
include-data-files = "rare/resources/languages=rare/resources/laguanges=*.qm"
windows-icon-from-ico = "rare/resources/images/Rare.ico"
windows-company-name = "Rare"
windows-product-name = "Rare"
windows-file-version = "1.9.0"
windows-product-version = "1.9.0"
windows-disable-console = true

View file

@ -117,4 +117,12 @@ if __name__ == "__main__":
if "__compiled__" not in globals():
sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute()))
# If we are on Windows, and we are in a "compiled" GUI application form
# stdout (and stderr?) will be None. So to avoid `'NoneType' object has no attribute 'write'`
# errors, redirect both of them to devnull
if os.name == "nt" and (getattr(sys, "frozen", False) or ("__compiled__" in globals())):
f = open(os.devnull, 'w')
sys.stdout = f
sys.stderr = f
main()

View file

@ -20,7 +20,7 @@ from rare.shared import (
ArgumentsSingleton,
)
from rare.shared.rare_core import RareCore
from rare.utils import legendary_utils, config_helper, paths
from rare.utils import config_helper, paths
from rare.widgets.rare_app import RareApp
logger = logging.getLogger("Rare")
@ -60,23 +60,23 @@ class App(RareApp):
self.load_translator(lang)
# set Application name for settings
self.mainwindow: Optional[MainWindow] = None
self.main_window: Optional[MainWindow] = None
self.launch_dialog: Optional[LaunchDialog] = None
self.timer = QTimer()
self.timer: Optional[QTimer] = None
# launch app
self.launch_dialog = LaunchDialog(parent=None)
self.launch_dialog.quit_app.connect(self.launch_dialog.close)
self.launch_dialog.quit_app.connect(lambda ec: exit(ec))
self.launch_dialog.quit_app.connect(lambda x: sys.exit(x))
self.launch_dialog.start_app.connect(self.start_app)
self.launch_dialog.start_app.connect(self.launch_dialog.close)
self.launch_dialog.login()
def poke_timer(self):
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1])
dt_now = datetime.utcnow()
td = abs(dt_exp - dt_now)
self.timer.timeout.connect(self.re_login)
self.timer.start(int(td.total_seconds() - 60) * 1000)
def re_login(self):
@ -86,36 +86,18 @@ class App(RareApp):
except requests.exceptions.ConnectionError:
self.timer.start(3000) # try again if no connection
return
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1])
dt_now = datetime.utcnow()
td = abs(dt_exp - dt_now)
self.timer.start(int(td.total_seconds() - 60) * 1000)
self.poke_timer()
def start_app(self):
for igame in self.core.get_installed_list():
if not os.path.exists(igame.install_path):
# lk; since install_path is lost anyway, set keep_files to True
# lk: to avoid spamming the log with "file not found" errors
legendary_utils.uninstall_game(self.core, igame.app_name, keep_files=True)
logger.info(f"Uninstalled {igame.title}, because no game files exist")
continue
# lk: games that don't have an override and can't find their executable due to case sensitivity
# lk: will still erroneously require verification. This might need to be removed completely
# lk: or be decoupled from the verification requirement
if override_exe := self.core.lgd.config.get(igame.app_name, "override_exe", fallback=""):
igame_executable = override_exe
else:
igame_executable = igame.executable
if not os.path.exists(os.path.join(igame.install_path, igame_executable.replace("\\", "/").lstrip("/"))):
igame.needs_verification = True
self.core.lgd.set_installed_game(igame.app_name, igame)
logger.info(f"{igame.title} needs verification")
self.timer = QTimer()
self.timer.timeout.connect(self.re_login)
self.poke_timer()
self.mainwindow = MainWindow()
self.mainwindow.exit_app.connect(self.exit_app)
self.main_window = MainWindow()
self.main_window.exit_app.connect(self.exit_app)
if not self.args.silent:
self.mainwindow.show()
self.main_window.show()
if self.args.test_start:
self.exit_app(0)
@ -127,9 +109,9 @@ class App(RareApp):
self.timer.stop()
self.timer.deleteLater()
self.timer = None
if self.mainwindow is not None:
self.mainwindow.close()
self.mainwindow = None
if self.main_window is not None:
self.main_window.close()
self.main_window = None
self.rare_core.deleteLater()
del self.rare_core
self.processEvents()

View file

@ -1,22 +1,26 @@
import os
import platform
from logging import getLogger
from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings
from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings, pyqtSlot
from PyQt5.QtWidgets import QDialog, QApplication
from legendary.models.game import Game
from requests.exceptions import ConnectionError, HTTPError
from rare.components.dialogs.login import LoginDialog
from rare.models.apiresults import ApiResults
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton, ImageManagerSingleton
from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog
from rare.utils import legendary_utils
from rare.utils.misc import CloudWorker
from rare.widgets.elide_label import ElideLabel
logger = getLogger("LaunchDialog")
class LaunchWorker(QRunnable):
class Signals(QObject):
progress = pyqtSignal(int)
progress = pyqtSignal(int, str)
result = pyqtSignal(object, str)
finished = pyqtSignal()
@ -26,16 +30,41 @@ class LaunchWorker(QRunnable):
self.signals = LaunchWorker.Signals()
self.core = LegendaryCoreSingleton()
def run(self):
def run_real(self):
pass
def run(self):
self.run_real()
self.signals.deleteLater()
class ImageWorker(LaunchWorker):
# FIXME: this is a middle-ground solution for concurrent downloads
class DownloadSlot(QObject):
def __init__(self, signals: LaunchWorker.Signals):
super(ImageWorker.DownloadSlot, self).__init__()
self.signals = signals
self.counter = 0
self.length = 0
@pyqtSlot(object)
def counter_inc(self, game: Game):
self.signals.progress.emit(
int(self.counter / self.length * 75),
self.tr("Downloading image for <b>{}</b>").format(game.app_title)
)
self.counter += 1
def __init__(self):
super(ImageWorker, self).__init__()
# FIXME: this is a middle-ground solution for concurrent downloads
self.dl_slot = ImageWorker.DownloadSlot(self.signals)
self.image_manager = ImageManagerSingleton()
def run(self):
def tr(self, t) -> str:
return QApplication.instance().translate(self.__class__.__name__, t)
def run_real(self):
# Download Images
games, dlcs = self.core.get_game_and_dlc_list(update_assets=True, skip_ue=False)
self.signals.result.emit((games, dlcs), "gamelist")
@ -47,12 +76,46 @@ class ImageWorker(LaunchWorker):
game_list = games + dlc_list + na_games + na_dlc_list
self.dl_slot.length = len(game_list)
for i, game in enumerate(game_list):
if game.app_title == "Unreal Engine":
game.app_title += f" {game.app_name.split('_')[-1]}"
self.core.lgd.set_game_meta(game.app_name, game)
self.image_manager.download_image_blocking(game)
self.signals.progress.emit(int(i / len(game_list) * 100))
# self.image_manager.download_image_blocking(game)
self.image_manager.download_image(game, self.dl_slot.counter_inc, priority=0)
# FIXME: this is a middle-ground solution for concurrent downloads
while self.dl_slot.counter < len(game_list):
QApplication.instance().processEvents()
self.dl_slot.deleteLater()
igame_list = self.core.get_installed_list(include_dlc=True)
# FIXME: incorporate installed game status checking here for now, still slow
for i, igame in enumerate(igame_list):
self.signals.progress.emit(
int(i / len(igame_list) * 25) + 75,
self.tr("Validating install for <b>{}</b>").format(igame.title)
)
if not os.path.exists(igame.install_path):
# lk; since install_path is lost anyway, set keep_files to True
# lk: to avoid spamming the log with "file not found" errors
legendary_utils.uninstall_game(self.core, igame.app_name, keep_files=True)
logger.info(f"Uninstalled {igame.title}, because no game files exist")
continue
# lk: games that don't have an override and can't find their executable due to case sensitivity
# lk: will still erroneously require verification. This might need to be removed completely
# lk: or be decoupled from the verification requirement
if override_exe := self.core.lgd.config.get(igame.app_name, "override_exe", fallback=""):
igame_executable = override_exe
else:
igame_executable = igame.executable
if not os.path.exists(os.path.join(igame.install_path, igame_executable.replace("\\", "/").lstrip("/"))):
igame.needs_verification = True
self.core.lgd.set_installed_game(igame.app_name, igame)
logger.info(f"{igame.title} needs verification")
# FIXME: end
self.signals.progress.emit(100, self.tr("Launching Rare"))
self.signals.finished.emit()
@ -61,7 +124,7 @@ class ApiRequestWorker(LaunchWorker):
super(ApiRequestWorker, self).__init__()
self.settings = QSettings()
def run(self) -> None:
def run_real(self) -> None:
if self.settings.value("mac_meta", platform.system() == "Darwin", bool):
try:
result = self.core.get_game_and_dlc_list(update_assets=False, platform="Mac")
@ -102,12 +165,15 @@ class LaunchDialog(QDialog):
self.ui = Ui_LaunchDialog()
self.ui.setupUi(self)
self.progress_info = ElideLabel(parent=self)
self.layout().addWidget(self.progress_info)
self.core = LegendaryCoreSingleton()
self.args = ArgumentsSingleton()
self.thread_pool = QThreadPool().globalInstance()
self.api_results = ApiResults()
self.login_dialog = LoginDialog(core=self.core, parent=self)
self.login_dialog = LoginDialog(core=self.core, parent=parent)
def login(self):
do_launch = True
@ -146,12 +212,12 @@ class LaunchDialog(QDialog):
self.thread_pool.start(api_worker)
def launch(self):
self.progress_info.setText(self.tr("Preparing Rare"))
if not self.args.offline:
self.ui.image_info.setText(self.tr("Downloading Images"))
image_worker = ImageWorker()
image_worker.signals.result.connect(self.handle_api_worker_result)
image_worker.signals.progress.connect(self.update_image_progbar)
image_worker.signals.progress.connect(self.update_progress)
# lk: start the api requests worker after the manifests have been downloaded
# lk: to avoid force updating the assets twice and causing inconsistencies
image_worker.signals.finished.connect(self.start_api_requests)
@ -206,14 +272,15 @@ class LaunchDialog(QDialog):
if self.api_results:
self.finish()
def update_image_progbar(self, i: int):
self.ui.image_prog_bar.setValue(i)
@pyqtSlot(int, str)
def update_progress(self, i: int, m: str):
self.ui.progress_bar.setValue(i)
self.progress_info.setText(m)
def finish(self):
self.completed += 1
if self.completed == 2:
logger.info("App starting")
self.ui.image_info.setText(self.tr("Starting..."))
ApiResultsSingleton(self.api_results)
self.completed += 1
self.start_app.emit()

View file

@ -63,8 +63,8 @@ class GameInfo(QWidget, Ui_GameInfo):
self.lbl_grade.setVisible(False)
self.grade.setVisible(False)
else:
self.steam_worker = SteamWorker(self.core)
self.steam_worker.signals.rating_signal.connect(self.grade.setText)
self.steam_worker: SteamWorker = SteamWorker(self.core)
self.steam_worker.signals.rating.connect(self.grade.setText)
self.steam_worker.setAutoDelete(False)
self.game_actions_stack.setCurrentIndex(0)

View file

@ -135,11 +135,15 @@ class MoveGamePopUp(QWidget):
return True, dir_selected, str()
def update_game(self, app_name):
igame = self.core.get_installed_game(app_name, False)
igame = self.core.get_installed_game(app_name, skip_sync=True)
if igame is None:
return
self.install_path = igame.install_path
# FIXME: Make edit_func lighter instead of blocking signals
self.move_path_edit.line_edit.blockSignals(True)
self.move_path_edit.setText(igame.install_path)
# FIXME: Make edit_func lighter instead of blocking signals
self.move_path_edit.line_edit.blockSignals(False)
self.warn_overwriting.setText(
self.tr("Moving here will overwrite the dir/file {}/").format(Path(self.install_path).stem)
)

View file

@ -87,7 +87,7 @@ class UninstalledInfo(QWidget, Ui_GameInfo):
self.install_button.clicked.connect(self.install_game)
if platform.system() != "Windows":
self.steam_worker = SteamWorker(self.core)
self.steam_worker.signals.rating_signal.connect(self.grade.setText)
self.steam_worker.signals.rating.connect(self.grade.setText)
self.steam_worker.setAutoDelete(False)
else:

View file

@ -1,9 +1,8 @@
from __future__ import annotations
import hashlib
import json
import pickle
import zlib
# from concurrent import futures
from logging import getLogger
from pathlib import Path
from typing import TYPE_CHECKING
@ -30,6 +29,8 @@ from rare.lgndr.core import LegendaryCore
from rare.models.signals import GlobalSignals
from rare.utils.paths import image_dir, resources_path
# from requests_futures.sessions import FuturesSession
if TYPE_CHECKING:
pass
@ -86,8 +87,8 @@ class ImageSize:
class ImageManager(QObject):
class Worker(QRunnable):
class Signals(QObject):
# str: app_name
completed = pyqtSignal(str)
# object: Game
completed = pyqtSignal(object)
def __init__(self, func: Callable, updates: List, json_data: Dict, game: Game):
super(ImageManager.Worker, self).__init__()
@ -101,7 +102,7 @@ class ImageManager(QObject):
def run(self):
self.func(self.updates, self.json_data, self.game)
logger.debug(f" Emitting singal for game {self.game.app_name} - {self.game.app_title}")
self.signals.completed.emit(self.game.app_name)
self.signals.completed.emit(self.game)
def __init__(self, signals: GlobalSignals, core: LegendaryCore):
# lk: the ordering in __img_types matters for the order of fallbacks
@ -179,7 +180,7 @@ class ImageManager(QObject):
return updates, json_data
def __download(self, updates, json_data, game) -> bool:
def __download(self, updates, json_data, game, use_async: bool = False) -> bool:
# Decompress existing image.cache
if not self.__img_cache(game.app_name).is_file():
cache_data = dict(zip(self.__img_types, [None] * len(self.__img_types)))
@ -193,6 +194,22 @@ class ImageManager(QObject):
if cache_data[image["type"]] is None or json_data[image["type"]] != image["md5"]
]
# Download
# # lk: Keep this so I don't have to go looking for it again,
# # lk: it might be useful in the future.
# if use_async and len(updates) > 1:
# session = FuturesSession(max_workers=len(self.__img_types))
# image_requests = []
# for image in updates:
# logger.info(f"Downloading {image['type']} for {game.app_title}")
# json_data[image["type"]] = image["md5"]
# payload = {"resize": 1, "w": ImageSize.Image.size.width(), "h": ImageSize.Image.size.height()}
# req = session.get(image["url"], params=payload)
# req.image_type = image["type"]
# image_requests.append(req)
# for req in futures.as_completed(image_requests):
# cache_data[req.image_type] = req.result().content
# else:
for image in updates:
logger.info(f"Downloading {image['type']} for {game.app_title}")
json_data[image["type"]] = image["md5"]
@ -291,18 +308,18 @@ class ImageManager(QObject):
return data
def download_image(
self, game: Game, load_callback: Callable[[], None], priority: int, force: bool = False
self, game: Game, load_callback: Callable[[Game], None], priority: int, force: bool = False
) -> None:
updates, json_data = self.__prepare_download(game, force)
if not updates:
load_callback()
load_callback(game)
return
if updates and game.app_name not in self.__worker_app_names:
image_worker = ImageManager.Worker(self.__download, updates, json_data, game)
self.__worker_app_names.append(game.app_name)
image_worker.signals.completed.connect(load_callback)
image_worker.signals.completed.connect(lambda app_name: self.__worker_app_names.remove(app_name))
image_worker.signals.completed.connect(lambda g: self.__worker_app_names.remove(g.app_name))
self.threadpool.start(image_worker, priority)
def download_image_blocking(self, game: Game, force: bool = False) -> None:
@ -310,7 +327,7 @@ class ImageManager(QObject):
if not updates:
return
if updates:
self.__download(updates, json_data, game)
self.__download(updates, json_data, game, use_async=True)
def __get_cover(
self, container: Union[Type[QPixmap], Type[QImage]], app_name: str, color: bool = True

View file

@ -1,32 +1,36 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'launch_dialog.ui'
# Form implementation generated from reading ui file 'rare/ui/components/dialogs/launch_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_LaunchDialog(object):
def setupUi(self, LaunchDialog):
LaunchDialog.setObjectName("LaunchDialog")
LaunchDialog.resize(400, 168)
self.verticalLayout = QtWidgets.QVBoxLayout(LaunchDialog)
self.verticalLayout.setObjectName("verticalLayout")
LaunchDialog.resize(400, 160)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(LaunchDialog.sizePolicy().hasHeightForWidth())
LaunchDialog.setSizePolicy(sizePolicy)
LaunchDialog.setMinimumSize(QtCore.QSize(400, 160))
LaunchDialog.setMaximumSize(QtCore.QSize(400, 160))
self.launch_dialog_layout = QtWidgets.QVBoxLayout(LaunchDialog)
self.launch_dialog_layout.setObjectName("launch_dialog_layout")
self.title_label = QtWidgets.QLabel(LaunchDialog)
self.title_label.setObjectName("title_label")
self.verticalLayout.addWidget(self.title_label)
self.image_prog_bar = QtWidgets.QProgressBar(LaunchDialog)
self.image_prog_bar.setProperty("value", 0)
self.image_prog_bar.setObjectName("image_prog_bar")
self.verticalLayout.addWidget(self.image_prog_bar)
self.image_info = QtWidgets.QLabel(LaunchDialog)
self.image_info.setObjectName("image_info")
self.verticalLayout.addWidget(self.image_info)
self.launch_dialog_layout.addWidget(self.title_label)
self.progress_bar = QtWidgets.QProgressBar(LaunchDialog)
self.progress_bar.setProperty("value", 0)
self.progress_bar.setObjectName("progress_bar")
self.launch_dialog_layout.addWidget(self.progress_bar)
self.retranslateUi(LaunchDialog)
QtCore.QMetaObject.connectSlotsByName(LaunchDialog)
@ -35,7 +39,6 @@ class Ui_LaunchDialog(object):
_translate = QtCore.QCoreApplication.translate
LaunchDialog.setWindowTitle(_translate("LaunchDialog", "Launching Rare"))
self.title_label.setText(_translate("LaunchDialog", "<h2>Launching Rare</h2>"))
self.image_info.setText(_translate("LaunchDialog", "Downloading images"))
if __name__ == "__main__":

View file

@ -7,13 +7,31 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>168</height>
<height>160</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>160</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>160</height>
</size>
</property>
<property name="windowTitle">
<string>Launching Rare</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="launch_dialog_layout">
<item>
<widget class="QLabel" name="title_label">
<property name="text">
@ -22,19 +40,12 @@
</widget>
</item>
<item>
<widget class="QProgressBar" name="image_prog_bar">
<widget class="QProgressBar" name="progress_bar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="image_info">
<property name="text">
<string>Downloading images</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View file

@ -16,7 +16,7 @@ url = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
class SteamWorker(QRunnable):
class Signals(QObject):
rating_signal = pyqtSignal(str)
rating = pyqtSignal(str)
app_name: str = ""
@ -39,7 +39,7 @@ class SteamWorker(QRunnable):
self.app_name = app_name
def run(self) -> None:
self.signals.rating_signal.emit(
self.signals.rating.emit(
self.ratings.get(get_rating(self.app_name), self.ratings["fail"])
)

7
requirements-dev.txt Normal file
View file

@ -0,0 +1,7 @@
build
wheel
black
toml
nuitka
ordered-set
PyQt5-stubs

View file

@ -47,5 +47,5 @@ setuptools.setup(
python_requires=">=3.8",
entry_points=dict(console_scripts=["rare=rare.__main__:main"]),
install_requires=requirements,
extras_require=optional_reqs
extras_require=optional_reqs,
)