785aaf648e
If a game was partially installed and it was imported through the import functionality, if `repair_and_update` is specified it will report `0` download size if there is no real update to be done. Fix it by detecting the need for an update explicitly. This will also force games that have failed verification to also update while repairing them, fixing the long-standing issue of repairing an older version of a game and then doing the update in a separate step.
306 lines
11 KiB
Python
306 lines
11 KiB
Python
import datetime
|
|
from logging import getLogger
|
|
from typing import List, Dict
|
|
|
|
from PyQt5.QtCore import QThread, pyqtSignal, QSettings
|
|
from PyQt5.QtWidgets import (
|
|
QWidget,
|
|
QMessageBox,
|
|
QVBoxLayout,
|
|
QLabel,
|
|
QPushButton,
|
|
QGroupBox,
|
|
)
|
|
from legendary.core import LegendaryCore
|
|
from legendary.models.game import Game, InstalledGame
|
|
|
|
from rare.components.dialogs.install_dialog import InstallDialog
|
|
from rare.components.tabs.downloads.dl_queue_widget import DlQueueWidget, DlWidget
|
|
from rare.components.tabs.downloads.download_thread import DownloadThread
|
|
from rare.lgndr.downloading import UIUpdate
|
|
from rare.models.install import InstallOptionsModel, InstallQueueItemModel
|
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
|
from rare.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab
|
|
from rare.utils.misc import get_size
|
|
|
|
logger = getLogger("Download")
|
|
|
|
|
|
class DownloadsTab(QWidget, Ui_DownloadsTab):
|
|
thread: QThread
|
|
dl_queue: List[InstallQueueItemModel] = []
|
|
dl_status = pyqtSignal(int)
|
|
|
|
def __init__(self, updates: list):
|
|
super(DownloadsTab, self).__init__()
|
|
self.setupUi(self)
|
|
self.core = LegendaryCoreSingleton()
|
|
self.signals = GlobalSignalsSingleton()
|
|
self.active_game: Game = None
|
|
self.analysis = None
|
|
|
|
self.kill_button.clicked.connect(self.stop_download)
|
|
|
|
self.queue_widget = DlQueueWidget()
|
|
self.queue_widget.update_list.connect(self.update_dl_queue)
|
|
self.queue_scroll_contents_layout.addWidget(self.queue_widget)
|
|
|
|
self.updates = QGroupBox(self.tr("Updates"))
|
|
self.updates.setObjectName("updates_group")
|
|
self.update_layout = QVBoxLayout(self.updates)
|
|
self.queue_scroll_contents_layout.addWidget(self.updates)
|
|
|
|
self.update_widgets: Dict[str, UpdateWidget] = {}
|
|
|
|
self.update_text = QLabel(self.tr("No updates available"))
|
|
self.update_layout.addWidget(self.update_text)
|
|
self.update_text.setVisible(len(updates) == 0)
|
|
|
|
for app_name in updates:
|
|
self.add_update(app_name)
|
|
|
|
self.queue_widget.item_removed.connect(self.queue_item_removed)
|
|
|
|
self.signals.install_game.connect(self.get_install_options)
|
|
self.signals.game_uninstalled.connect(self.queue_item_removed)
|
|
self.signals.game_uninstalled.connect(self.remove_update)
|
|
|
|
self.signals.add_download.connect(
|
|
lambda app_name: self.add_update(app_name)
|
|
)
|
|
self.signals.game_uninstalled.connect(self.game_uninstalled)
|
|
|
|
self.reset_infos()
|
|
|
|
def queue_item_removed(self, app_name):
|
|
if w := self.update_widgets.get(app_name):
|
|
w.update_button.setDisabled(False)
|
|
w.update_with_settings.setDisabled(False)
|
|
|
|
def add_update(self, app_name: str):
|
|
if old_widget := self.update_widgets.get(app_name, False):
|
|
old_widget.deleteLater()
|
|
self.update_widgets.pop(app_name)
|
|
widget = UpdateWidget(self.core, app_name, self)
|
|
self.update_layout.addWidget(widget)
|
|
self.update_widgets[app_name] = widget
|
|
widget.update_signal.connect(self.get_install_options)
|
|
if QSettings().value("auto_update", False, bool):
|
|
self.get_install_options(
|
|
InstallOptionsModel(app_name=app_name, update=True, silent=True)
|
|
)
|
|
widget.update_button.setDisabled(True)
|
|
self.update_text.setVisible(False)
|
|
|
|
def game_uninstalled(self, app_name):
|
|
# game in dl_queue
|
|
for i, item in enumerate(self.dl_queue):
|
|
if item.options.app_name == app_name:
|
|
self.dl_queue.pop(i)
|
|
self.queue_widget.update_queue(self.dl_queue)
|
|
break
|
|
|
|
# if game is updating
|
|
if self.active_game and self.active_game.app_name == app_name:
|
|
self.stop_download()
|
|
|
|
# game has available update
|
|
if app_name in self.update_widgets.keys():
|
|
self.remove_update(app_name)
|
|
|
|
def remove_update(self, app_name):
|
|
if w := self.update_widgets.get(app_name):
|
|
w.deleteLater()
|
|
self.update_widgets.pop(app_name)
|
|
|
|
if len(self.update_widgets) == 0:
|
|
self.update_text.setVisible(True)
|
|
|
|
self.signals.update_download_tab_text.emit()
|
|
|
|
def update_dl_queue(self, dl_queue):
|
|
self.dl_queue = dl_queue
|
|
|
|
def stop_download(self):
|
|
self.thread.kill()
|
|
|
|
def install_game(self, queue_item: InstallQueueItemModel):
|
|
if self.active_game is None:
|
|
self.start_installation(queue_item)
|
|
else:
|
|
self.dl_queue.append(queue_item)
|
|
self.queue_widget.update_queue(self.dl_queue)
|
|
|
|
def start_installation(self, queue_item: InstallQueueItemModel):
|
|
if self.dl_queue:
|
|
self.dl_queue.pop(0)
|
|
self.queue_widget.update_queue(self.dl_queue)
|
|
self.active_game = queue_item.download.game
|
|
self.thread = DownloadThread(self.core, queue_item)
|
|
self.thread.status.connect(self.status)
|
|
self.thread.statistics.connect(self.statistics)
|
|
self.thread.start()
|
|
self.kill_button.setDisabled(False)
|
|
self.analysis = queue_item.download.analysis
|
|
self.dl_name.setText(self.active_game.app_title)
|
|
|
|
self.signals.installation_started.emit(self.active_game.app_name)
|
|
|
|
def status(self, text):
|
|
if text == "finish":
|
|
self.dl_name.setText(self.tr("Download finished. Reload library"))
|
|
logger.info(f"Download finished: {self.active_game.app_title}")
|
|
|
|
game = self.active_game
|
|
self.active_game = None
|
|
|
|
if self.dl_queue:
|
|
if self.dl_queue[0].download.game.app_name == game.app_name:
|
|
self.dl_queue.pop(0)
|
|
self.queue_widget.update_queue(self.dl_queue)
|
|
|
|
if game.app_name in self.update_widgets.keys():
|
|
igame = self.core.get_installed_game(game.app_name)
|
|
if (
|
|
self.core.get_asset(
|
|
game.app_name, igame.platform, False
|
|
).build_version
|
|
== igame.version
|
|
):
|
|
self.remove_update(game.app_name)
|
|
|
|
self.signals.send_notification.emit(game.app_title)
|
|
self.signals.update_gamelist.emit([game.app_name])
|
|
self.signals.update_download_tab_text.emit()
|
|
|
|
self.signals.installation_finished.emit(True, game.app_name)
|
|
|
|
self.reset_infos()
|
|
|
|
if len(self.dl_queue) != 0:
|
|
self.start_installation(self.dl_queue[0])
|
|
else:
|
|
self.queue_widget.update_queue(self.dl_queue)
|
|
|
|
elif text[:5] == "error":
|
|
QMessageBox.warning(self, "warn", f"Download error: {text[6:]}")
|
|
|
|
elif text == "stop":
|
|
self.reset_infos()
|
|
if w := self.update_widgets.get(self.active_game.app_name):
|
|
w.update_button.setDisabled(False)
|
|
w.update_with_settings.setDisabled(False)
|
|
self.signals.installation_finished.emit(False, self.active_game.app_name)
|
|
self.active_game = None
|
|
if self.dl_queue:
|
|
self.start_installation(self.dl_queue[0])
|
|
|
|
def reset_infos(self):
|
|
self.kill_button.setDisabled(True)
|
|
self.dl_name.setText(self.tr("No active download"))
|
|
self.progress_bar.setValue(0)
|
|
self.dl_speed.setText("n/a")
|
|
self.time_left.setText("n/a")
|
|
self.cache_used.setText("n/a")
|
|
self.downloaded.setText("n/a")
|
|
self.analysis = None
|
|
|
|
def statistics(self, ui_update: UIUpdate):
|
|
self.progress_bar.setValue(
|
|
100 * ui_update.total_downloaded // self.analysis.dl_size
|
|
)
|
|
self.dl_speed.setText(f"{get_size(ui_update.download_speed)}/s")
|
|
self.cache_used.setText(
|
|
f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}"
|
|
)
|
|
self.downloaded.setText(
|
|
f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}"
|
|
)
|
|
self.time_left.setText(self.get_time(ui_update.estimated_time_left))
|
|
self.signals.dl_progress.emit(
|
|
100 * ui_update.total_downloaded // self.analysis.dl_size
|
|
)
|
|
|
|
def get_time(self, seconds: int) -> str:
|
|
return str(datetime.timedelta(seconds=seconds))
|
|
|
|
def on_install_dialog_closed(self, download_item: InstallQueueItemModel):
|
|
if download_item:
|
|
self.install_game(download_item)
|
|
# lk: In case the download in comming from game verification/repair
|
|
if w := self.update_widgets.get(download_item.options.app_name):
|
|
w.update_button.setDisabled(True)
|
|
w.update_with_settings.setDisabled(True)
|
|
self.signals.set_main_tab_index.emit(1)
|
|
else:
|
|
if w := self.update_widgets.get(download_item.options.app_name):
|
|
w.update_button.setDisabled(False)
|
|
w.update_with_settings.setDisabled(False)
|
|
|
|
def get_install_options(self, options: InstallOptionsModel):
|
|
|
|
install_dialog = InstallDialog(
|
|
InstallQueueItemModel(options=options),
|
|
update=options.update,
|
|
silent=options.silent,
|
|
parent=self,
|
|
)
|
|
install_dialog.result_ready.connect(self.on_install_dialog_closed)
|
|
install_dialog.execute()
|
|
|
|
def start_download(self, download_item: InstallQueueItemModel):
|
|
downloads = (
|
|
len(self.downloadTab.dl_queue)
|
|
+ len(self.downloadTab.update_widgets.keys())
|
|
+ 1
|
|
)
|
|
self.setTabText(
|
|
1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "")
|
|
)
|
|
self.setCurrentIndex(1)
|
|
self.downloadTab.install_game(download_item)
|
|
self.games_tab.start_download(download_item.options.app_name)
|
|
|
|
@property
|
|
def is_download_active(self):
|
|
return self.active_game is not None
|
|
|
|
|
|
class UpdateWidget(QWidget):
|
|
update_signal = pyqtSignal(InstallOptionsModel)
|
|
|
|
def __init__(self, core: LegendaryCore, app_name: str, parent):
|
|
super(UpdateWidget, self).__init__(parent=parent)
|
|
self.core = core
|
|
self.game: Game = core.get_game(app_name)
|
|
self.igame: InstalledGame = self.core.get_installed_game(app_name)
|
|
|
|
layout = QVBoxLayout()
|
|
self.title = QLabel(self.igame.title)
|
|
layout.addWidget(self.title)
|
|
|
|
self.update_button = QPushButton(self.tr("Update Game"))
|
|
self.update_button.clicked.connect(lambda: self.update_game(True))
|
|
self.update_with_settings = QPushButton("Update with settings")
|
|
self.update_with_settings.clicked.connect(lambda: self.update_game(False))
|
|
layout.addWidget(self.update_button)
|
|
layout.addWidget(self.update_with_settings)
|
|
layout.addWidget(
|
|
QLabel(
|
|
self.tr("Version: <b>")
|
|
+ self.igame.version
|
|
+ "</b> \u2B9E <b>"
|
|
+ self.game.app_version(self.igame.platform)
|
|
+ "</b>"
|
|
)
|
|
)
|
|
|
|
self.setLayout(layout)
|
|
|
|
def update_game(self, auto: bool):
|
|
self.update_button.setDisabled(True)
|
|
self.update_with_settings.setDisabled(True)
|
|
self.update_signal.emit(
|
|
InstallOptionsModel(app_name=self.igame.app_name, silent=auto)
|
|
) # True if settings
|