1
0
Fork 0
mirror of synced 2024-06-18 02:24:43 +12:00
Rare/rare/components/tabs/downloads/download_thread.py

231 lines
9 KiB
Python
Raw Normal View History

2021-04-08 02:50:36 +12:00
import os
import platform
2021-04-08 02:50:36 +12:00
import queue
import subprocess
import sys
2021-04-08 02:50:36 +12:00
import time
from logging import getLogger
from queue import Empty
2021-04-08 02:50:36 +12:00
import psutil
2021-04-08 02:50:36 +12:00
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMessageBox
from legendary.core import LegendaryCore
from legendary.models.downloading import UIUpdate, WriterTask
from rare import shared
from rare.utils.models import InstallQueueItemModel
from rare.utils.utils import create_desktop_link
2021-04-08 02:50:36 +12:00
logger = getLogger("DownloadThread")
2021-04-08 02:50:36 +12:00
class DownloadThread(QThread):
status = pyqtSignal(str)
statistics = pyqtSignal(UIUpdate)
def __init__(self, core: LegendaryCore, queue_item: InstallQueueItemModel):
2021-04-08 02:50:36 +12:00
super(DownloadThread, self).__init__()
self.core = core
self.dlm = queue_item.download.dlmanager
self.no_install = queue_item.options.no_install
self.status_q = queue_item.status_q
self.igame = queue_item.download.igame
self.repair = queue_item.download.repair
self.repair_file = queue_item.download.repair_file
self.queue_item = queue_item
self._kill = False
2021-04-08 02:50:36 +12:00
def run(self):
start_time = time.time()
dl_stopped = False
2021-04-08 02:50:36 +12:00
try:
self.dlm.start()
time.sleep(1)
while self.dlm.is_alive():
if self._kill:
self.status.emit("stop")
logger.info("Download stopping...")
# The code below is a temporary solution.
# It should be removed once legendary supports stopping downloads more gracefully.
self.dlm.running = False
# send conditions to unlock threads if they aren't already
for cond in self.dlm.conditions:
with cond:
cond.notify()
# make sure threads are dead.
for t in self.dlm.threads:
t.join(timeout=5.0)
if t.is_alive():
2021-12-24 22:09:50 +13:00
logger.warning(f"Thread did not terminate! {repr(t)}")
# clean up all the queues, otherwise this process won't terminate properly
2021-12-24 22:09:50 +13:00
for name, q in zip(
(
"Download jobs",
"Writer jobs",
"Download results",
"Writer results",
),
(
self.dlm.dl_worker_queue,
self.dlm.writer_queue,
self.dlm.dl_result_q,
self.dlm.writer_result_q,
),
):
logger.debug(f'Cleaning up queue "{name}"')
try:
while True:
_ = q.get_nowait()
except Empty:
q.close()
q.join_thread()
except AttributeError:
2021-12-24 22:09:50 +13:00
logger.warning(f"Queue {name} did not close")
if self.dlm.writer_queue:
# cancel installation
2021-12-24 22:09:50 +13:00
self.dlm.writer_queue.put_nowait(WriterTask("", kill=True))
# forcibly kill DL workers that are not actually dead yet
for child in self.dlm.children:
if child.exitcode is None:
child.terminate()
if self.dlm.shared_memory:
# close up shared memory
self.dlm.shared_memory.close()
self.dlm.shared_memory.unlink()
self.dlm.shared_memory = None
self.dlm.kill()
# force kill any threads that are somehow still alive
for proc in psutil.process_iter():
# check whether the process name matches
2021-12-24 22:09:50 +13:00
if (
sys.platform in ["linux", "darwin"]
and proc.name() == "DownloadThread"
):
proc.kill()
2021-12-24 22:09:50 +13:00
elif (
sys.platform == "win32"
and proc.name() == "python.exe"
and proc.create_time() >= start_time
):
proc.kill()
logger.info("Download stopped. It can be continued later.")
dl_stopped = True
2021-04-08 02:50:36 +12:00
try:
if not dl_stopped:
self.statistics.emit(self.status_q.get(timeout=1))
2021-04-08 02:50:36 +12:00
except queue.Empty:
pass
self.dlm.join()
except Exception as e:
2021-12-24 22:09:50 +13:00
logger.error(
f"Installation failed after {time.time() - start_time:.02f} seconds: {e}"
)
self.status.emit(f"error {e}")
2021-04-08 02:50:36 +12:00
return
else:
if dl_stopped:
return
2021-04-08 02:50:36 +12:00
self.status.emit("dl_finished")
end_t = time.time()
logger.info(f"Download finished in {end_t - start_time}s")
2021-04-08 02:50:36 +12:00
game = self.core.get_game(self.igame.app_name)
2021-05-21 09:00:38 +12:00
if self.queue_item.options.overlay:
shared.signals.overlay_installation_finished.emit()
self.core.finish_overlay_install(self.igame)
self.status.emit("finish")
return
if not self.no_install:
2021-05-21 09:00:38 +12:00
postinstall = self.core.install_game(self.igame)
if postinstall:
self._handle_postinstall(postinstall, self.igame)
dlcs = self.core.get_dlc_for_game(self.igame.app_name)
if dlcs:
2021-12-24 22:09:50 +13:00
print("The following DLCs are available for this game:")
2021-05-21 09:00:38 +12:00
for dlc in dlcs:
2021-12-24 22:09:50 +13:00
print(
f" - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})"
)
print(
"Manually installing DLCs works the same; just use the DLC app name instead."
)
2021-05-21 09:00:38 +12:00
# install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
# TODO
if game.supports_cloud_saves and not game.is_dlc:
2021-12-24 22:09:50 +13:00
logger.info(
'This game supports cloud saves, syncing is handled by the "sync-saves" command.'
)
logger.info(
f'To download saves for this game run "legendary sync-saves {game.app_name}"'
)
2021-04-08 02:50:36 +12:00
old_igame = self.core.get_installed_game(game.app_name)
if old_igame and self.repair and os.path.exists(self.repair_file):
if old_igame.needs_verification:
old_igame.needs_verification = False
self.core.install_game(old_igame)
2021-12-24 22:09:50 +13:00
logger.debug("Removing repair file.")
2021-04-08 02:50:36 +12:00
os.remove(self.repair_file)
if old_igame and old_igame.install_tags != self.igame.install_tags:
old_igame.install_tags = self.igame.install_tags
2021-12-24 22:09:50 +13:00
self.logger.info("Deleting now untagged files.")
2021-04-08 02:50:36 +12:00
self.core.uninstall_tag(old_igame)
self.core.install_game(old_igame)
if not self.queue_item.options.update and self.queue_item.options.create_shortcut:
if not create_desktop_link(self.queue_item.options.app_name, self.core, "desktop"):
# maybe add it to download summary, to show in finished downloads
pass
else:
logger.info("Desktop shortcut written")
2021-04-08 02:50:36 +12:00
self.status.emit("finish")
def _handle_postinstall(self, postinstall, igame):
2021-12-24 22:09:50 +13:00
print("This game lists the following prequisites to be installed:")
print(
f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}'
)
if platform.system() == "Windows":
2021-12-24 22:09:50 +13:00
if (
QMessageBox.question(
self,
"",
"Do you want to install the prequisites",
QMessageBox.Yes | QMessageBox.No,
)
== QMessageBox.Yes
):
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)
2021-12-24 22:09:50 +13:00
subprocess.call([fullpath, postinstall["args"]], cwd=work_dir)
2021-04-08 02:50:36 +12:00
else:
self.core.prereq_installed(self.igame.app_name)
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
def kill(self):
self._kill = True