show mid-install status per-title

This commit is contained in:
Ian Burgwin
2021-02-09 21:21:34 -08:00
parent 9c777adf26
commit 37112682a0
2 changed files with 49 additions and 15 deletions

View File

@@ -23,7 +23,7 @@ from pyctr.type.cdn import CDNError
from pyctr.type.cia import CIAError from pyctr.type.cia import CIAError
from pyctr.type.tmd import TitleMetadataError 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: if TYPE_CHECKING:
from os import PathLike from os import PathLike
@@ -75,6 +75,15 @@ default_movable_sed_path = find_first_file([join(file_parent, 'movable.sed')])
if default_seeddb_path: if default_seeddb_path:
load_seeddb(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): class ConsoleFrame(ttk.Frame):
def __init__(self, parent: tk.BaseWidget = None, starting_lines: 'List[str]' = None): 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 = ttk.Treeview(treeview_frame, yscrollcommand=treeview_scrollbar.set)
self.treeview.grid(row=0, column=0, sticky=tk.NSEW) 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.column('filepath', width=200, anchor=tk.W)
self.treeview.heading('filepath', text='File path') 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.heading('titleid', text='Title ID')
self.treeview.column('titlename', width=150, anchor=tk.W) self.treeview.column('titlename', width=150, anchor=tk.W)
self.treeview.heading('titlename', text='Title name') 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) treeview_scrollbar.configure(command=self.treeview.yview)
@@ -494,6 +505,9 @@ class CustomInstallGUI(ttk.Frame):
return False return False
return self.b9_loaded 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): def add_cia(self, path):
if not self.check_b9_loaded(): if not self.check_b9_loaded():
# this shouldn't happen # this shouldn't happen
@@ -516,7 +530,8 @@ class CustomInstallGUI(ttk.Frame):
title_name = reader.contents[0].exefs.icon.get_app_title().short_desc title_name = reader.contents[0].exefs.icon.get_app_title().short_desc
except: except:
title_name = '(No title)' 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 self.readers[path] = reader
return True, '' return True, ''
@@ -596,12 +611,13 @@ class CustomInstallGUI(ttk.Frame):
'Continue?'): 'Continue?'):
return return
self.disable_buttons()
if not len(self.readers): if not len(self.readers):
self.show_error('There are no titles added to install.') self.show_error('There are no titles added to install.')
return return
for path in self.readers.keys():
self.update_status(path, InstallStatus.Waiting)
self.disable_buttons()
self.log('Starting install...') self.log('Starting install...')
if taskbar: if taskbar:
@@ -613,10 +629,10 @@ class CustomInstallGUI(ttk.Frame):
overwrite_saves=self.overwrite_saves_var.get() == 1) overwrite_saves=self.overwrite_saves_var.get() == 1)
# use the treeview which has been sorted alphabetically # use the treeview which has been sorted alphabetically
#installer.readers = self.readers.values()
readers_final = [] readers_final = []
for k in self.treeview.get_children(): 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 installer.readers = readers_final
@@ -652,6 +668,7 @@ class CustomInstallGUI(ttk.Frame):
installer.event.update_percentage += ci_update_percentage installer.event.update_percentage += ci_update_percentage
installer.event.on_error += ci_on_error installer.event.on_error += ci_on_error
installer.event.on_cia_start += ci_on_cia_start installer.event.on_cia_start += ci_on_cia_start
installer.event.update_status += self.update_status
if self.skip_contents_var.get() != 1: if self.skip_contents_var.get() != 1:
total_size, free_space = installer.check_size() total_size, free_space = installer.check_size()

View File

@@ -5,6 +5,7 @@
# You can find the full license text in LICENSE.md in the root of this project. # You can find the full license text in LICENSE.md in the root of this project.
from argparse import ArgumentParser from argparse import ArgumentParser
from enum import Enum
from os import makedirs, rename, scandir from os import makedirs, rename, scandir
from os.path import dirname, join, isdir, isfile from os.path import dirname, join, isdir, isfile
from random import randint from random import randint
@@ -21,7 +22,7 @@ import subprocess
if TYPE_CHECKING: if TYPE_CHECKING:
from os import PathLike from os import PathLike
from typing import List, Union from typing import List, Union, Tuple
from events import Events from events import Events
@@ -74,6 +75,15 @@ class InvalidCIFinishError(Exception):
pass 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]'): def get_free_space(path: 'Union[PathLike, bytes, str]'):
if is_windows: if is_windows:
lpSectorsPerCluster = c_ulonglong(0) lpSectorsPerCluster = c_ulonglong(0)
@@ -199,7 +209,7 @@ class CustomInstall:
self.crypto = CryptoEngine(boot9=boot9) self.crypto = CryptoEngine(boot9=boot9)
self.crypto.setup_sd_key_from_file(movable) self.crypto.setup_sd_key_from_file(movable)
self.seeddb = seeddb self.seeddb = seeddb
self.readers: 'List[Union[CDNReader, CIAReader]]' = [] self.readers: 'List[Tuple[Union[CDNReader, CIAReader], Union[PathLike, bytes, str]]]' = []
self.sd = sd self.sd = sd
self.skip_contents = skip_contents self.skip_contents = skip_contents
self.overwrite_saves = overwrite_saves self.overwrite_saves = overwrite_saves
@@ -249,12 +259,12 @@ class CustomInstall:
if reader.tmd.title_id.startswith('00048'): # DSiWare if reader.tmd.title_id.startswith('00048'): # DSiWare
self.log(f'Skipping {reader.tmd.title_id} - DSiWare is not supported') self.log(f'Skipping {reader.tmd.title_id} - DSiWare is not supported')
continue continue
readers.append(reader) readers.append((reader, path))
self.readers = readers self.readers = readers
def check_size(self): def check_size(self):
total_size = 0 total_size = 0
for r in self.readers: for r, _ in self.readers:
total_size += get_install_size(r) total_size += get_install_size(r)
free_space = get_free_space(self.sd) free_space = get_free_space(self.sd)
@@ -335,9 +345,11 @@ class CustomInstall:
install_state = {'installed': [], 'failed': []} install_state = {'installed': [], 'failed': []}
# Now loop through all provided cia files # 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.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}') 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) makedirs(temp_title_root, exist_ok=True)
@@ -384,6 +396,7 @@ class CustomInstall:
temp_content_root = join(temp_title_root, 'content') temp_content_root = join(temp_title_root, 'content')
if not self.skip_contents: if not self.skip_contents:
self.event.update_status(path, InstallStatus.Writing)
makedirs(join(temp_content_root, 'cmd'), exist_ok=True) makedirs(join(temp_content_root, 'cmd'), exist_ok=True)
if cia.tmd.save_size: if cia.tmd.save_size:
makedirs(join(temp_title_root, 'data'), exist_ok=True) makedirs(join(temp_title_root, 'data'), exist_ok=True)
@@ -424,6 +437,7 @@ class CustomInstall:
install_state['failed'].append(display_title) install_state['failed'].append(display_title)
rename(temp_title_root, temp_title_root + '-corrupted') rename(temp_title_root, temp_title_root + '-corrupted')
do_continue = True do_continue = True
self.event.update_status(path, InstallStatus.Failed)
break break
if do_continue: if do_continue:
@@ -535,6 +549,7 @@ class CustomInstall:
b'\0' * 0x2c b'\0' * 0x2c
] ]
self.event.update_status(path, InstallStatus.Finishing)
if isdir(title_root): if isdir(title_root):
self.log(f'Removing original install at {title_root}...') self.log(f'Removing original install at {title_root}...')
rmtree(title_root) rmtree(title_root)
@@ -564,8 +579,10 @@ class CustomInstall:
for l in pformat(out.args).split('\n'): for l in pformat(out.args).split('\n'):
self.log(l) self.log(l)
install_state['failed'].append(display_title) install_state['failed'].append(display_title)
self.event.update_status(path, InstallStatus.Failed)
install_state['installed'].append(display_title) else:
install_state['installed'].append(display_title)
self.event.update_status(path, InstallStatus.Done)
copied = False copied = False
if install_state['installed']: if install_state['installed']: