diff --git a/legendary/cli.py b/legendary/cli.py index 40754ae..07a4011 100644 --- a/legendary/cli.py +++ b/legendary/cli.py @@ -3,9 +3,11 @@ import argparse import csv +import datetime import json import logging import os +import queue import shlex import subprocess import time @@ -501,13 +503,13 @@ class LegendaryCLI: logger.info(f'Launch parameters: {shlex.join(params)}') logger.info(f'Working directory: {cwd}') if env: - logger.info('Environment overrides:', env) + logger.info(f'Environment overrides: {env}') else: logger.info(f'Launching {app_name}...') logger.debug(f'Launch parameters: {shlex.join(params)}') logger.debug(f'Working directory: {cwd}') if env: - logger.debug('Environment overrides:', env) + logger.debug(f'Environment overrides: {env}') subprocess.Popen(params, cwd=cwd, env=env) def install_game(self, args): @@ -522,15 +524,15 @@ class LegendaryCLI: logger.error(f'Update requested for "{args.app_name}", but app not installed!') exit(1) + status_queue = MPQueue() logger.info('Preparing download...') - # todo use status queue to print progress from CLI - # This has become a little ridiculous hasn't it? try: dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download( app_name=args.app_name, base_path=args.base_path, force=args.force, no_install=args.no_install, + status_q=status_queue, max_shm=args.shared_memory, max_workers=args.max_workers, game_folder=args.game_folder, @@ -578,6 +580,22 @@ class LegendaryCLI: dlm.proc_debug = args.dlm_debug dlm.start() + time.sleep(1) + while dlm.is_alive(): + try: + status = status_queue.get(timeout=0.1) + logger.info(f'= Progress: {status.progress:.02f}% ({status.processed_chunks}/{status.chunk_tasks}), ' + f'Running for {str(datetime.timedelta(seconds=status.runtime))}, ' + f'ETA: {str(datetime.timedelta(seconds=status.estimated_time_left))}') + logger.info(f' - Downloaded: {status.total_downloaded / 1024 / 1024:.02f} MiB, ' + f'Written: {status.total_written / 1024 / 1024:.02f} MiB') + logger.info(f' - Cache usage: {status.cache_usage} MiB, active tasks: {status.active_tasks}') + logger.info(f' + Download\t- {status.download_speed / 1024 / 1024:.02f} MiB/s (raw) ' + f'/ {status.download_decompressed_speed / 1024 / 1024:.02f} MiB/s (decompressed)') + logger.info(f' + Disk\t- {status.write_speed / 1024 / 1024:.02f} MiB/s (write) / ' + f'{status.read_speed / 1024 / 1024:.02f} MiB/s (read)') + except queue.Empty: + pass dlm.join() except Exception as e: end_t = time.time() diff --git a/legendary/core.py b/legendary/core.py index fd50894..9551958 100644 --- a/legendary/core.py +++ b/legendary/core.py @@ -24,8 +24,8 @@ from legendary.lfs.lgndry import LGDLFS from legendary.utils.lfs import clean_filename, delete_folder, delete_filelist, validate_files from legendary.models.downloading import AnalysisResult, ConditionCheckResult from legendary.models.egl import EGLManifest -from legendary.models.exceptions import * -from legendary.models.game import * +from legendary.models.exceptions import InvalidCredentialsError +from legendary.models.game import GameAsset, Game, InstalledGame, SaveGameFile, SaveGameStatus, VerifyResult from legendary.models.json_manifest import JSONManifest from legendary.models.manifest import Manifest, ManifestMeta from legendary.models.chunk import Chunk diff --git a/legendary/downloader/manager.py b/legendary/downloader/manager.py index 0b84d2a..4e9bb5d 100644 --- a/legendary/downloader/manager.py +++ b/legendary/downloader/manager.py @@ -695,35 +695,30 @@ class DLManager(Process): total_avail = len(self.sms) total_used = (num_shared_memory_segments - total_avail) * (self.analysis.biggest_chunk / 1024 / 1024) - if runtime and processed_chunks: - rt_hours, runtime = int(runtime // 3600), runtime % 3600 - rt_minutes, rt_seconds = int(runtime // 60), int(runtime % 60) - + try: average_speed = processed_chunks / runtime estimate = (num_chunk_tasks - processed_chunks) / average_speed - hours, estimate = int(estimate // 3600), estimate % 3600 - minutes, seconds = int(estimate // 60), int(estimate % 60) - else: - hours = minutes = seconds = 0 - rt_hours = rt_minutes = rt_seconds = 0 - - self.log.info(f'= Progress: {perc:.02f}% ({processed_chunks}/{num_chunk_tasks}), ' - f'Running for {rt_hours:02d}:{rt_minutes:02d}:{rt_seconds:02d}, ' - f'ETA: {hours:02d}:{minutes:02d}:{seconds:02d}') - self.log.info(f' - Downloaded: {total_dl / 1024 / 1024:.02f} MiB, ' - f'Written: {total_write / 1024 / 1024:.02f} MiB') - self.log.info(f' - Cache usage: {total_used} MiB, active tasks: {self.active_tasks}') - self.log.info(f' + Download\t- {dl_speed / 1024 / 1024:.02f} MiB/s (raw) ' - f'/ {dl_unc_speed / 1024 / 1024:.02f} MiB/s (decompressed)') - self.log.info(f' + Disk\t- {w_speed / 1024 / 1024:.02f} MiB/s (write) / ' - f'{r_speed / 1024 / 1024:.02f} MiB/s (read)') + except ZeroDivisionError: + average_speed = estimate = 0 + # TODO set current_filename argument of UIUpdate # send status update to back to instantiator (if queue exists) if self.status_queue: try: self.status_queue.put(UIUpdate( - progress=perc, download_speed=dl_unc_speed, write_speed=w_speed, read_speed=r_speed, - memory_usage=total_used * 1024 * 1024 + progress=perc, + runtime=round(runtime), + estimated_time_left=round(estimate), + processed_chunks=processed_chunks, + chunk_tasks=num_chunk_tasks, + total_downloaded=total_dl, + total_written=total_write, + cache_usage=total_used, + active_tasks=self.active_tasks, + download_speed=dl_speed, + download_decompressed_speed=dl_unc_speed, + write_speed=w_speed, + read_speed=r_speed, ), timeout=1.0) except Exception as e: self.log.warning(f'Failed to send status update to queue: {e!r}') diff --git a/legendary/models/downloading.py b/legendary/models/downloading.py index b538090..918f2ae 100644 --- a/legendary/models/downloading.py +++ b/legendary/models/downloading.py @@ -70,14 +70,23 @@ class UIUpdate: Status update object sent from the manager to the CLI/GUI to update status indicators """ - def __init__(self, progress, download_speed, write_speed, read_speed, - memory_usage, current_filename=''): + def __init__(self, progress, runtime, estimated_time_left, processed_chunks, chunk_tasks, + total_downloaded, total_written, cache_usage, active_tasks, download_speed, + download_decompressed_speed, write_speed, read_speed, current_filename=''): self.progress = progress + self.runtime = runtime + self.estimated_time_left = estimated_time_left + self.processed_chunks = processed_chunks + self.chunk_tasks = chunk_tasks + self.total_downloaded = total_downloaded + self.total_written = total_written + self.cache_usage = cache_usage + self.active_tasks = active_tasks self.download_speed = download_speed + self.download_decompressed_speed = download_decompressed_speed self.write_speed = write_speed self.read_speed = read_speed self.current_filename = current_filename - self.memory_usage = memory_usage class SharedMemorySegment: