LaunchDialog: Add a middle-ground solution for concurrent image downloads
This commit is contained in:
parent
681b3013ad
commit
cf5fd415e5
3 changed files with 62 additions and 22 deletions
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
|
@ -88,7 +88,6 @@ jobs:
|
|||
python-version: '3.9'
|
||||
- name: Dependencies
|
||||
run: |
|
||||
pip3 install certifi==2022.6.15
|
||||
pip3 install -r requirements.txt
|
||||
pip3 install -r requirements-presence.txt
|
||||
- name: Build Dependencies
|
||||
|
|
|
@ -2,8 +2,9 @@ import os
|
|||
import platform
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings, pyqtSlot, QCoreApplication
|
||||
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
|
||||
|
@ -29,19 +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 * 50),
|
||||
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 tr(self, t) -> str:
|
||||
return QCoreApplication.translate(self.__class__.__name__, t)
|
||||
return QApplication.instance().translate(self.__class__.__name__, t)
|
||||
|
||||
def run(self):
|
||||
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")
|
||||
|
@ -53,20 +76,21 @@ 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.signals.progress.emit(
|
||||
int(i / len(game_list) * 50),
|
||||
self.tr("Downloading image for <b>{}</b>").format(game.app_title)
|
||||
)
|
||||
self.image_manager.download_image_blocking(game)
|
||||
# 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
|
||||
# if igame := self.core.get_installed_game(game.app_name, skip_sync=True):
|
||||
for i, igame in enumerate(igame_list):
|
||||
self.signals.progress.emit(
|
||||
int(i / len(igame_list) * 50) + 50,
|
||||
|
@ -100,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")
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue