Merge pull request #242 from loathingKernel/fixups
Nuitka and another round of fixes for Windows
This commit is contained in:
commit
4ef755fa2e
44
.github/workflows/checks.yml
vendored
Normal file
44
.github/workflows/checks.yml
vendored
Normal 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
|
|
@ -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: |
|
83
.github/workflows/release.yml
vendored
83
.github/workflows/release.yml
vendored
|
@ -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
1
misc/nuitka_rare.bat
Normal 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
5
misc/pip_upgrade_venv.py
Normal 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)
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
48
rare/app.py
48
rare/app.py
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__":
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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
7
requirements-dev.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
build
|
||||
wheel
|
||||
black
|
||||
toml
|
||||
nuitka
|
||||
ordered-set
|
||||
PyQt5-stubs
|
Loading…
Reference in a new issue