2021-04-08 02:50:36 +12:00
|
|
|
import os
|
2021-06-21 07:55:31 +12:00
|
|
|
import platform
|
2021-04-08 02:50:36 +12:00
|
|
|
import queue
|
|
|
|
import time
|
2022-08-03 11:33:50 +12:00
|
|
|
from dataclasses import dataclass
|
|
|
|
from enum import IntEnum
|
2021-04-08 02:50:36 +12:00
|
|
|
from logging import getLogger
|
2022-08-03 11:33:50 +12:00
|
|
|
from typing import List, Optional, Dict
|
2021-04-08 02:50:36 +12:00
|
|
|
|
2022-04-13 10:56:45 +12:00
|
|
|
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
|
|
|
|
|
2022-08-01 23:31:07 +12:00
|
|
|
from rare.lgndr.cli import LegendaryCLI
|
2023-01-21 13:15:06 +13:00
|
|
|
from rare.lgndr.core import LegendaryCore
|
2022-10-27 14:00:48 +13:00
|
|
|
from rare.lgndr.glue.monkeys import DLManagerSignals
|
|
|
|
from rare.lgndr.models.downloading import UIUpdate
|
2023-01-21 13:15:06 +13:00
|
|
|
from rare.models.game import RareGame
|
2023-01-28 09:05:39 +13:00
|
|
|
from rare.models.install import InstallQueueItemModel, InstallOptionsModel
|
2021-04-08 02:50:36 +12:00
|
|
|
|
2022-01-13 11:47:35 +13:00
|
|
|
logger = getLogger("DownloadThread")
|
2021-04-08 02:50:36 +12:00
|
|
|
|
|
|
|
|
2023-01-21 13:15:06 +13:00
|
|
|
class DlResultCode(IntEnum):
|
|
|
|
ERROR = 1
|
|
|
|
STOPPED = 2
|
|
|
|
FINISHED = 3
|
|
|
|
|
2023-01-28 09:05:39 +13:00
|
|
|
|
2023-01-21 13:15:06 +13:00
|
|
|
@dataclass
|
|
|
|
class DlResultModel:
|
2023-01-28 09:05:39 +13:00
|
|
|
options: InstallOptionsModel
|
2023-01-21 13:15:06 +13:00
|
|
|
code: DlResultCode = DlResultCode.ERROR
|
|
|
|
message: str = ""
|
|
|
|
dlcs: Optional[List[Dict]] = None
|
|
|
|
sync_saves: bool = False
|
|
|
|
tip_url: str = ""
|
2023-02-08 00:41:59 +13:00
|
|
|
shortcut: bool = False
|
|
|
|
shortcut_name: str = ""
|
|
|
|
shortcut_title: str = ""
|
2023-01-21 13:15:06 +13:00
|
|
|
|
2023-01-28 09:05:39 +13:00
|
|
|
|
2023-01-21 13:15:06 +13:00
|
|
|
class DlThread(QThread):
|
|
|
|
result = pyqtSignal(DlResultModel)
|
2023-03-17 01:04:12 +13:00
|
|
|
progress = pyqtSignal(UIUpdate, object)
|
2023-01-21 13:15:06 +13:00
|
|
|
|
|
|
|
def __init__(self, item: InstallQueueItemModel, rgame: RareGame, core: LegendaryCore, debug: bool = False):
|
|
|
|
super(DlThread, self).__init__()
|
|
|
|
self.dlm_signals: DLManagerSignals = DLManagerSignals()
|
2022-07-24 10:06:35 +12:00
|
|
|
self.core: LegendaryCore = core
|
|
|
|
self.item: InstallQueueItemModel = item
|
2023-03-17 01:04:12 +13:00
|
|
|
self.dl_size = item.download.analysis.dl_size
|
2023-01-21 13:15:06 +13:00
|
|
|
self.rgame = rgame
|
|
|
|
self.debug = debug
|
|
|
|
|
2023-03-13 23:30:27 +13:00
|
|
|
def __finish(self, result):
|
2023-01-21 13:15:06 +13:00
|
|
|
if result.code == DlResultCode.FINISHED:
|
|
|
|
self.rgame.set_installed(True)
|
|
|
|
self.rgame.state = RareGame.State.IDLE
|
|
|
|
self.rgame.signals.progress.finish.emit(not result.code == DlResultCode.FINISHED)
|
|
|
|
self.result.emit(result)
|
2021-04-08 02:50:36 +12:00
|
|
|
|
2023-12-11 09:06:44 +13:00
|
|
|
def __status_callback(self, status: UIUpdate):
|
|
|
|
self.progress.emit(status, self.dl_size)
|
|
|
|
self.rgame.signals.progress.update.emit(int(status.progress))
|
|
|
|
|
2021-04-08 02:50:36 +12:00
|
|
|
def run(self):
|
2022-08-09 06:06:58 +12:00
|
|
|
cli = LegendaryCLI(self.core)
|
|
|
|
self.item.download.dlm.logging_queue = cli.logging_queue
|
2023-01-21 13:15:06 +13:00
|
|
|
self.item.download.dlm.proc_debug = self.debug
|
2023-01-28 09:05:39 +13:00
|
|
|
result = DlResultModel(self.item.options)
|
2022-08-03 11:33:50 +12:00
|
|
|
start_t = time.time()
|
2021-04-08 02:50:36 +12:00
|
|
|
try:
|
2022-08-09 06:06:58 +12:00
|
|
|
self.item.download.dlm.start()
|
2023-01-21 13:15:06 +13:00
|
|
|
self.rgame.state = RareGame.State.DOWNLOADING
|
|
|
|
self.rgame.signals.progress.start.emit()
|
2021-04-08 02:50:36 +12:00
|
|
|
time.sleep(1)
|
2022-08-09 06:06:58 +12:00
|
|
|
while self.item.download.dlm.is_alive():
|
2021-04-08 02:50:36 +12:00
|
|
|
try:
|
2023-12-11 09:06:44 +13:00
|
|
|
self.__status_callback(self.item.download.dlm.status_queue.get(timeout=1.0))
|
2021-04-08 02:50:36 +12:00
|
|
|
except queue.Empty:
|
|
|
|
pass
|
2022-08-03 11:33:50 +12:00
|
|
|
if self.dlm_signals.update:
|
|
|
|
try:
|
2022-08-09 06:06:58 +12:00
|
|
|
self.item.download.dlm.signals_queue.put(self.dlm_signals, block=False, timeout=1.0)
|
2022-08-03 11:33:50 +12:00
|
|
|
except queue.Full:
|
|
|
|
pass
|
2022-08-09 06:06:58 +12:00
|
|
|
time.sleep(self.item.download.dlm.update_interval / 10)
|
|
|
|
self.item.download.dlm.join()
|
2021-04-08 02:50:36 +12:00
|
|
|
except Exception as e:
|
2023-01-21 13:15:06 +13:00
|
|
|
self.kill()
|
|
|
|
self.item.download.dlm.join()
|
2022-08-03 11:33:50 +12:00
|
|
|
end_t = time.time()
|
|
|
|
logger.error(f"Installation failed after {end_t - start_t:.02f} seconds.")
|
|
|
|
logger.warning(f"The following exception occurred while waiting for the downloader to finish: {e!r}.")
|
2023-01-21 13:15:06 +13:00
|
|
|
result.code = DlResultCode.ERROR
|
|
|
|
result.message = f"{e!r}"
|
2023-03-13 23:30:27 +13:00
|
|
|
self.__finish(result)
|
2021-04-08 02:50:36 +12:00
|
|
|
return
|
|
|
|
else:
|
|
|
|
end_t = time.time()
|
2022-08-03 11:33:50 +12:00
|
|
|
if self.dlm_signals.kill is True:
|
|
|
|
logger.info(f"Download stopped after {end_t - start_t:.02f} seconds.")
|
2023-01-21 13:15:06 +13:00
|
|
|
result.code = DlResultCode.STOPPED
|
2023-03-13 23:30:27 +13:00
|
|
|
self.__finish(result)
|
2022-08-03 11:33:50 +12:00
|
|
|
return
|
|
|
|
logger.info(f"Download finished in {end_t - start_t:.02f} seconds.")
|
|
|
|
|
2023-01-21 13:15:06 +13:00
|
|
|
result.code = DlResultCode.FINISHED
|
2021-05-21 09:00:38 +12:00
|
|
|
|
2022-07-24 10:06:35 +12:00
|
|
|
if self.item.options.overlay:
|
|
|
|
self.core.finish_overlay_install(self.item.download.igame)
|
2023-03-13 23:30:27 +13:00
|
|
|
self.__finish(result)
|
2022-01-13 11:46:46 +13:00
|
|
|
return
|
|
|
|
|
2022-07-24 10:06:35 +12:00
|
|
|
if not self.item.options.no_install:
|
|
|
|
postinstall = self.core.install_game(self.item.download.igame)
|
2021-05-21 09:00:38 +12:00
|
|
|
if postinstall:
|
2022-08-03 11:33:50 +12:00
|
|
|
# LegendaryCLI(self.core)._handle_postinstall(
|
|
|
|
# postinstall,
|
|
|
|
# self.item.download.igame,
|
|
|
|
# False,
|
2022-09-01 20:49:42 +12:00
|
|
|
# self.item.options.install_prereqs,
|
2022-08-03 11:33:50 +12:00
|
|
|
# )
|
2022-07-24 10:06:35 +12:00
|
|
|
self._handle_postinstall(postinstall, self.item.download.igame)
|
2021-05-21 09:00:38 +12:00
|
|
|
|
2022-07-24 10:06:35 +12:00
|
|
|
dlcs = self.core.get_dlc_for_game(self.item.download.igame.app_name)
|
2022-08-09 02:25:50 +12:00
|
|
|
if dlcs and not self.item.options.skip_dlcs:
|
2023-01-21 13:15:06 +13:00
|
|
|
result.dlcs = []
|
2021-05-21 09:00:38 +12:00
|
|
|
for dlc in dlcs:
|
2023-01-21 13:15:06 +13:00
|
|
|
result.dlcs.append(
|
2022-08-12 22:17:53 +12:00
|
|
|
{
|
|
|
|
"app_name": dlc.app_name,
|
|
|
|
"app_title": dlc.app_title,
|
|
|
|
"app_version": dlc.app_version(self.item.options.platform),
|
|
|
|
}
|
2021-12-24 22:09:50 +13:00
|
|
|
)
|
2021-05-21 09:00:38 +12:00
|
|
|
|
2022-08-12 22:17:53 +12:00
|
|
|
if (
|
|
|
|
self.item.download.game.supports_cloud_saves
|
|
|
|
or self.item.download.game.supports_mac_cloud_saves
|
|
|
|
) and not self.item.download.game.is_dlc:
|
2023-01-21 13:15:06 +13:00
|
|
|
result.sync_saves = True
|
2022-08-12 22:17:53 +12:00
|
|
|
|
|
|
|
# show tip again after installation finishes so users hopefully actually see it
|
|
|
|
if tip_url := self.core.get_game_tip(self.item.download.igame.app_name):
|
2023-01-21 13:15:06 +13:00
|
|
|
result.tip_url = tip_url
|
2022-08-03 11:33:50 +12:00
|
|
|
|
2022-08-09 06:06:58 +12:00
|
|
|
LegendaryCLI(self.core).install_game_cleanup(
|
2022-08-03 11:33:50 +12:00
|
|
|
self.item.download.game,
|
|
|
|
self.item.download.igame,
|
|
|
|
self.item.download.repair,
|
|
|
|
self.item.download.repair_file,
|
|
|
|
)
|
2022-02-05 10:12:00 +13:00
|
|
|
|
2022-08-03 11:33:50 +12:00
|
|
|
if not self.item.options.update and self.item.options.create_shortcut:
|
2023-02-08 00:41:59 +13:00
|
|
|
result.shortcut = True
|
|
|
|
result.shortcut_name = self.rgame.folder_name
|
|
|
|
result.shortcut_title = self.rgame.app_title
|
2022-08-03 11:33:50 +12:00
|
|
|
|
2023-03-13 23:30:27 +13:00
|
|
|
self.__finish(result)
|
2021-04-08 02:50:36 +12:00
|
|
|
|
|
|
|
def _handle_postinstall(self, postinstall, igame):
|
2022-09-18 21:52:15 +12:00
|
|
|
logger.info("This game lists the following prerequisites to be installed:")
|
2022-08-03 11:33:50 +12:00
|
|
|
logger.info(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
|
2021-06-21 07:55:31 +12:00
|
|
|
if platform.system() == "Windows":
|
2022-09-19 05:44:26 +12:00
|
|
|
if self.item.options.install_prereqs:
|
2022-08-12 22:17:53 +12:00
|
|
|
logger.info("Launching prerequisite executable..")
|
2021-04-08 02:50:36 +12:00
|
|
|
self.core.prereq_installed(igame.app_name)
|
2021-12-24 22:09:50 +13:00
|
|
|
req_path, req_exec = os.path.split(postinstall["path"])
|
2021-04-08 02:50:36 +12:00
|
|
|
work_dir = os.path.join(igame.install_path, req_path)
|
|
|
|
fullpath = os.path.join(work_dir, req_exec)
|
2022-04-13 10:56:45 +12:00
|
|
|
proc = QProcess()
|
|
|
|
proc.setProcessChannelMode(QProcess.MergedChannels)
|
|
|
|
proc.readyReadStandardOutput.connect(
|
2022-08-03 11:33:50 +12:00
|
|
|
lambda: logger.debug(str(proc.readAllStandardOutput().data(), "utf-8", "ignore"))
|
|
|
|
)
|
2022-09-18 23:55:33 +12:00
|
|
|
proc.setProgram(fullpath)
|
|
|
|
proc.setArguments(postinstall.get("args", "").split(" "))
|
2022-08-03 11:33:50 +12:00
|
|
|
proc.setWorkingDirectory(work_dir)
|
2022-09-18 23:55:33 +12:00
|
|
|
proc.start()
|
2022-04-13 10:56:45 +12:00
|
|
|
proc.waitForFinished() # wait, because it is inside the thread
|
2021-04-08 02:50:36 +12:00
|
|
|
else:
|
2021-12-24 22:09:50 +13:00
|
|
|
logger.info("Automatic installation not available on Linux.")
|
2021-04-08 02:50:36 +12:00
|
|
|
|
2021-04-12 03:03:55 +12:00
|
|
|
def kill(self):
|
2022-08-03 11:33:50 +12:00
|
|
|
self.dlm_signals.kill = True
|