From 37112682a0c72d983e694b738f9958edc15de74a Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Tue, 9 Feb 2021 21:21:34 -0800 Subject: [PATCH] show mid-install status per-title --- ci-gui.py | 33 +++++++++++++++++++++++++-------- custominstall.py | 31 ++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/ci-gui.py b/ci-gui.py index 905ab07..668a0c7 100644 --- a/ci-gui.py +++ b/ci-gui.py @@ -23,7 +23,7 @@ from pyctr.type.cdn import CDNError from pyctr.type.cia import CIAError from pyctr.type.tmd import TitleMetadataError -from custominstall import CustomInstall, CI_VERSION, load_cifinish, InvalidCIFinishError +from custominstall import CustomInstall, CI_VERSION, load_cifinish, InvalidCIFinishError, InstallStatus if TYPE_CHECKING: from os import PathLike @@ -75,6 +75,15 @@ default_movable_sed_path = find_first_file([join(file_parent, 'movable.sed')]) if default_seeddb_path: load_seeddb(default_seeddb_path) +statuses = { + InstallStatus.Waiting: 'Waiting', + InstallStatus.Starting: 'Starting', + InstallStatus.Writing: 'Writing', + InstallStatus.Finishing: 'Finishing', + InstallStatus.Done: 'Done', + InstallStatus.Failed: 'Failed', +} + class ConsoleFrame(ttk.Frame): def __init__(self, parent: tk.BaseWidget = None, starting_lines: 'List[str]' = None): @@ -418,14 +427,16 @@ class CustomInstallGUI(ttk.Frame): self.treeview = ttk.Treeview(treeview_frame, yscrollcommand=treeview_scrollbar.set) self.treeview.grid(row=0, column=0, sticky=tk.NSEW) - self.treeview.configure(columns=('filepath', 'titleid', 'titlename'), show='headings') + self.treeview.configure(columns=('filepath', 'titleid', 'titlename', 'status'), show='headings') self.treeview.column('filepath', width=200, anchor=tk.W) self.treeview.heading('filepath', text='File path') - self.treeview.column('titleid', width=50, anchor=tk.W) + self.treeview.column('titleid', width=70, anchor=tk.W) self.treeview.heading('titleid', text='Title ID') self.treeview.column('titlename', width=150, anchor=tk.W) self.treeview.heading('titlename', text='Title name') + self.treeview.column('status', width=20, anchor=tk.W) + self.treeview.heading('status', text='Status') treeview_scrollbar.configure(command=self.treeview.yview) @@ -494,6 +505,9 @@ class CustomInstallGUI(ttk.Frame): return False return self.b9_loaded + def update_status(self, path: 'Union[PathLike, bytes, str]', status: InstallStatus): + self.treeview.set(path, 'status', statuses[status]) + def add_cia(self, path): if not self.check_b9_loaded(): # this shouldn't happen @@ -516,7 +530,8 @@ class CustomInstallGUI(ttk.Frame): title_name = reader.contents[0].exefs.icon.get_app_title().short_desc except: title_name = '(No title)' - self.treeview.insert('', tk.END, text=path, iid=path, values=(path, reader.tmd.title_id, title_name)) + self.treeview.insert('', tk.END, text=path, iid=path, + values=(path, reader.tmd.title_id, title_name, statuses[InstallStatus.Waiting])) self.readers[path] = reader return True, '' @@ -596,12 +611,13 @@ class CustomInstallGUI(ttk.Frame): 'Continue?'): return - self.disable_buttons() - if not len(self.readers): self.show_error('There are no titles added to install.') return + for path in self.readers.keys(): + self.update_status(path, InstallStatus.Waiting) + self.disable_buttons() self.log('Starting install...') if taskbar: @@ -613,10 +629,10 @@ class CustomInstallGUI(ttk.Frame): overwrite_saves=self.overwrite_saves_var.get() == 1) # use the treeview which has been sorted alphabetically - #installer.readers = self.readers.values() readers_final = [] for k in self.treeview.get_children(): - readers_final.append(self.readers[self.treeview.set(k, 'filepath')]) + filepath = self.treeview.set(k, 'filepath') + readers_final.append((self.readers[filepath], filepath)) installer.readers = readers_final @@ -652,6 +668,7 @@ class CustomInstallGUI(ttk.Frame): installer.event.update_percentage += ci_update_percentage installer.event.on_error += ci_on_error installer.event.on_cia_start += ci_on_cia_start + installer.event.update_status += self.update_status if self.skip_contents_var.get() != 1: total_size, free_space = installer.check_size() diff --git a/custominstall.py b/custominstall.py index a731e58..5c0981a 100644 --- a/custominstall.py +++ b/custominstall.py @@ -5,6 +5,7 @@ # You can find the full license text in LICENSE.md in the root of this project. from argparse import ArgumentParser +from enum import Enum from os import makedirs, rename, scandir from os.path import dirname, join, isdir, isfile from random import randint @@ -21,7 +22,7 @@ import subprocess if TYPE_CHECKING: from os import PathLike - from typing import List, Union + from typing import List, Union, Tuple from events import Events @@ -74,6 +75,15 @@ class InvalidCIFinishError(Exception): pass +class InstallStatus(Enum): + Waiting = 0 + Starting = 1 + Writing = 2 + Finishing = 3 + Done = 4 + Failed = 5 + + def get_free_space(path: 'Union[PathLike, bytes, str]'): if is_windows: lpSectorsPerCluster = c_ulonglong(0) @@ -199,7 +209,7 @@ class CustomInstall: self.crypto = CryptoEngine(boot9=boot9) self.crypto.setup_sd_key_from_file(movable) self.seeddb = seeddb - self.readers: 'List[Union[CDNReader, CIAReader]]' = [] + self.readers: 'List[Tuple[Union[CDNReader, CIAReader], Union[PathLike, bytes, str]]]' = [] self.sd = sd self.skip_contents = skip_contents self.overwrite_saves = overwrite_saves @@ -249,12 +259,12 @@ class CustomInstall: if reader.tmd.title_id.startswith('00048'): # DSiWare self.log(f'Skipping {reader.tmd.title_id} - DSiWare is not supported') continue - readers.append(reader) + readers.append((reader, path)) self.readers = readers def check_size(self): total_size = 0 - for r in self.readers: + for r, _ in self.readers: total_size += get_install_size(r) free_space = get_free_space(self.sd) @@ -335,9 +345,11 @@ class CustomInstall: install_state = {'installed': [], 'failed': []} # Now loop through all provided cia files - for idx, cia in enumerate(self.readers): + for idx, info in enumerate(self.readers): + cia, path = info self.event.on_cia_start(idx) + self.event.update_status(path, InstallStatus.Starting) temp_title_root = join(self.sd, f'ci-install-temp-{cia.tmd.title_id}-{randint(0, 0xFFFFFFFF):08x}') makedirs(temp_title_root, exist_ok=True) @@ -384,6 +396,7 @@ class CustomInstall: temp_content_root = join(temp_title_root, 'content') if not self.skip_contents: + self.event.update_status(path, InstallStatus.Writing) makedirs(join(temp_content_root, 'cmd'), exist_ok=True) if cia.tmd.save_size: makedirs(join(temp_title_root, 'data'), exist_ok=True) @@ -424,6 +437,7 @@ class CustomInstall: install_state['failed'].append(display_title) rename(temp_title_root, temp_title_root + '-corrupted') do_continue = True + self.event.update_status(path, InstallStatus.Failed) break if do_continue: @@ -535,6 +549,7 @@ class CustomInstall: b'\0' * 0x2c ] + self.event.update_status(path, InstallStatus.Finishing) if isdir(title_root): self.log(f'Removing original install at {title_root}...') rmtree(title_root) @@ -564,8 +579,10 @@ class CustomInstall: for l in pformat(out.args).split('\n'): self.log(l) install_state['failed'].append(display_title) - - install_state['installed'].append(display_title) + self.event.update_status(path, InstallStatus.Failed) + else: + install_state['installed'].append(display_title) + self.event.update_status(path, InstallStatus.Done) copied = False if install_state['installed']: