mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-26 04:02:11 +00:00
[ci skip] nxdt_host: update to v0.5
Changes include: * Fix progress bar window not being automatically closed if a cancel request is received from the console in-between two different NSP file entry transfers. * Handle unit and divisor retrieval directly inside the ProgressBarWindow class. * Carry out a separate speed calculation whenever necessary to always display transfer rates in MiB/s. * Fix CLI mode. * Minor grammar corrections in the code.
This commit is contained in:
parent
392887c12f
commit
76de7b10fd
1 changed files with 71 additions and 32 deletions
|
@ -69,7 +69,7 @@ WINDOW_WIDTH = 500
|
||||||
WINDOW_HEIGHT = 470
|
WINDOW_HEIGHT = 470
|
||||||
|
|
||||||
# Application version.
|
# Application version.
|
||||||
APP_VERSION = '0.4'
|
APP_VERSION = '0.5'
|
||||||
|
|
||||||
# Copyright year.
|
# Copyright year.
|
||||||
COPYRIGHT_YEAR = '2020-2024'
|
COPYRIGHT_YEAR = '2020-2024'
|
||||||
|
@ -409,13 +409,14 @@ class ProgressBarWindow:
|
||||||
def __init__(self, bar_format: str = '', tk_parent: Any = None, window_title: str = '', window_resize: bool = False, window_protocol: Callable | None = None) -> None:
|
def __init__(self, bar_format: str = '', tk_parent: Any = None, window_title: str = '', window_resize: bool = False, window_protocol: Callable | None = None) -> None:
|
||||||
self.n: int = 0
|
self.n: int = 0
|
||||||
self.total: int = 0
|
self.total: int = 0
|
||||||
self.divider: float = 1.0
|
self.divisor: float = 1.0
|
||||||
self.total_div: float = 0
|
self.total_div: float = 0
|
||||||
self.prefix: str = ''
|
self.prefix: str = ''
|
||||||
self.unit: str = 'B'
|
self.unit: str = 'B'
|
||||||
self.bar_format = bar_format
|
self.bar_format = bar_format
|
||||||
self.start_time: float = 0
|
self.start_time: float = 0
|
||||||
self.elapsed_time: float = 0
|
self.prev_iter_time: float = 0
|
||||||
|
self.prev_n: int = 0
|
||||||
self.hwnd: int = 0
|
self.hwnd: int = 0
|
||||||
|
|
||||||
self.tk_parent = tk_parent
|
self.tk_parent = tk_parent
|
||||||
|
@ -455,22 +456,30 @@ class ProgressBarWindow:
|
||||||
if self.tk_window:
|
if self.tk_window:
|
||||||
self.tk_parent.after(0, self.tk_window.destroy)
|
self.tk_parent.after(0, self.tk_window.destroy)
|
||||||
|
|
||||||
def start(self, total: int, n: int = 0, divider: int = 1, prefix: str = '', unit: str = 'B') -> None:
|
def start(self, total: int, n: int = 0, prefix: str = '') -> None:
|
||||||
if (total <= 0) or (n < 0) or (divider < 1):
|
if (total <= 0) or (n < 0):
|
||||||
raise Exception('Invalid arguments!')
|
raise Exception('Invalid arguments!')
|
||||||
|
|
||||||
self.n = n
|
self.n = n
|
||||||
self.total = total
|
self.total = total
|
||||||
self.divider = float(divider)
|
|
||||||
self.total_div = (float(self.total) / self.divider)
|
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.unit = unit
|
|
||||||
|
# Get progress bar unit and unit divisor. These will be used to display and calculate size values using a specific size unit (B, KiB, MiB, GiB).
|
||||||
|
unit_and_divisor = utilsGetSizeUnitAndDivisor(self.total)
|
||||||
|
self.unit = unit_and_divisor[0]
|
||||||
|
self.divisor = float(unit_and_divisor[1])
|
||||||
|
|
||||||
|
self.total_div = (float(self.total) / self.divisor)
|
||||||
|
|
||||||
|
# Replace our custom rate variable with tqdm's rate_fmt if our unit is set to MiB.
|
||||||
|
if self.unit == 'MiB':
|
||||||
|
self.bar_format = self.bar_format.replace('__custom_rate_fmt__', '{rate_fmt}')
|
||||||
|
|
||||||
if self.tk_pbar:
|
if self.tk_pbar:
|
||||||
self.tk_pbar.configure(maximum=self.total_div, mode='determinate')
|
self.tk_pbar.configure(maximum=self.total_div, mode='determinate')
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
else:
|
else:
|
||||||
n_div = (float(self.n) / self.divider)
|
n_div = (float(self.n) / self.divisor)
|
||||||
self.pbar = tqdm(initial=n_div, total=self.total_div, unit=self.unit, dynamic_ncols=True, desc=self.prefix, bar_format=self.bar_format)
|
self.pbar = tqdm(initial=n_div, total=self.total_div, unit=self.unit, dynamic_ncols=True, desc=self.prefix, bar_format=self.bar_format)
|
||||||
|
|
||||||
def update(self, n: int) -> None:
|
def update(self, n: int) -> None:
|
||||||
|
@ -478,14 +487,19 @@ class ProgressBarWindow:
|
||||||
if cur_n > self.total:
|
if cur_n > self.total:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
cur_time = time.time()
|
||||||
|
cur_n_div = (float(cur_n) / self.divisor)
|
||||||
|
|
||||||
|
msg = self._format_speed(cur_time, cur_n)
|
||||||
|
if not msg:
|
||||||
|
self.n = cur_n
|
||||||
|
return
|
||||||
|
|
||||||
if self.tk_window:
|
if self.tk_window:
|
||||||
assert self.tk_text_var is not None
|
assert self.tk_text_var is not None
|
||||||
assert self.tk_n_var is not None
|
assert self.tk_n_var is not None
|
||||||
|
|
||||||
cur_n_div = (float(cur_n) / self.divider)
|
msg = tqdm.format_meter(n=cur_n_div, total=self.total_div, elapsed=(cur_time - self.start_time), prefix=self.prefix, unit=self.unit, bar_format=msg)
|
||||||
self.elapsed_time = (time.time() - self.start_time)
|
|
||||||
|
|
||||||
msg = tqdm.format_meter(n=cur_n_div, total=self.total_div, elapsed=self.elapsed_time, prefix=self.prefix, unit=self.unit, bar_format=self.bar_format)
|
|
||||||
|
|
||||||
self.tk_text_var.set(msg)
|
self.tk_text_var.set(msg)
|
||||||
self.tk_n_var.set(cur_n_div)
|
self.tk_n_var.set(cur_n_div)
|
||||||
|
@ -506,20 +520,43 @@ class ProgressBarWindow:
|
||||||
g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total)
|
g_taskbar.SetProgressValue(self.hwnd, cur_n, self.total)
|
||||||
else:
|
else:
|
||||||
assert self.pbar is not None
|
assert self.pbar is not None
|
||||||
n_div = (float(n) / self.divider)
|
|
||||||
self.pbar.update(n_div)
|
self.pbar.bar_format = msg
|
||||||
|
self.pbar.n = (float(self.n) / self.divisor)
|
||||||
|
self.pbar.update(float(n) / self.divisor)
|
||||||
|
|
||||||
self.n = cur_n
|
self.n = cur_n
|
||||||
|
|
||||||
|
def _format_speed(self, cur_time: float, cur_n: int) -> str:
|
||||||
|
# Short-circuit: return immediately if our unit is set to MiB. We'll let tqdm do its thing.
|
||||||
|
if self.unit == 'MiB':
|
||||||
|
return self.bar_format
|
||||||
|
|
||||||
|
# I absolutely hate to roll out my own speed calculation for the UI, but tqdm offers no way to use different units for the progress/total/rate values.
|
||||||
|
# Please forgive me.
|
||||||
|
last_iter_time = (cur_time - self.prev_iter_time)
|
||||||
|
if (self.prev_n > 0) and (last_iter_time != cur_time) and (last_iter_time < 1.0) and (cur_n < self.total):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
rate = (float(cur_n - self.prev_n) / 1048576.0)
|
||||||
|
if last_iter_time != cur_time:
|
||||||
|
rate /= last_iter_time
|
||||||
|
|
||||||
|
self.prev_n = cur_n
|
||||||
|
self.prev_iter_time = cur_time
|
||||||
|
|
||||||
|
return self.bar_format.replace('__custom_rate_fmt__', f'{rate:.2f} MiB/s')
|
||||||
|
|
||||||
def end(self) -> None:
|
def end(self) -> None:
|
||||||
self.n = 0
|
self.n = 0
|
||||||
self.total = 0
|
self.total = 0
|
||||||
self.divider = 1
|
self.divisor = 1
|
||||||
self.total_div = 0
|
self.total_div = 0
|
||||||
self.prefix = ''
|
self.prefix = ''
|
||||||
self.unit = 'B'
|
self.unit = 'B'
|
||||||
self.start_time = 0
|
self.start_time = 0
|
||||||
self.elapsed_time = 0
|
self.prev_iter_time = 0
|
||||||
|
self.prev_n = 0
|
||||||
|
|
||||||
if self.tk_window:
|
if self.tk_window:
|
||||||
assert self.tk_pbar is not None
|
assert self.tk_pbar is not None
|
||||||
|
@ -604,7 +641,6 @@ def usbGetDeviceEndpoints() -> bool:
|
||||||
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize
|
global g_usbEpIn, g_usbEpOut, g_usbEpMaxPacketSize
|
||||||
|
|
||||||
assert g_logger is not None
|
assert g_logger is not None
|
||||||
assert g_stopEvent is not None
|
|
||||||
|
|
||||||
cur_dev: Generator[usb.core.Device, Any, None] | None = None
|
cur_dev: Generator[usb.core.Device, Any, None] | None = None
|
||||||
prev_dev: usb.core.Device | None = None
|
prev_dev: usb.core.Device | None = None
|
||||||
|
@ -613,13 +649,15 @@ def usbGetDeviceEndpoints() -> bool:
|
||||||
usb_version = 0
|
usb_version = 0
|
||||||
|
|
||||||
if g_cliMode:
|
if g_cliMode:
|
||||||
g_logger.info(f'Please connect a Nintendo Switch console running {USB_DEV_PRODUCT}.')
|
g_logger.info(SERVER_START_MSG)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Check if the user decided to stop the server.
|
# Check if the user decided to stop the server.
|
||||||
if not g_cliMode and g_stopEvent.is_set():
|
if not g_cliMode:
|
||||||
g_stopEvent.clear()
|
assert g_stopEvent is not None
|
||||||
return False
|
if g_stopEvent.is_set():
|
||||||
|
g_stopEvent.clear()
|
||||||
|
return False
|
||||||
|
|
||||||
# Find a connected USB device with a matching VID/PID pair.
|
# Find a connected USB device with a matching VID/PID pair.
|
||||||
# Using == here to compare both device instances would also compare the backend, so we'll just compare certain elements manually.
|
# Using == here to compare both device instances would also compare the backend, so we'll just compare certain elements manually.
|
||||||
|
@ -680,7 +718,7 @@ def usbGetDeviceEndpoints() -> bool:
|
||||||
g_logger.debug(f'Max packet size: 0x{g_usbEpMaxPacketSize:X} (USB {usb_version >> 8}.{(usb_version & 0xFF) >> 4}).\n')
|
g_logger.debug(f'Max packet size: 0x{g_usbEpMaxPacketSize:X} (USB {usb_version >> 8}.{(usb_version & 0xFF) >> 4}).\n')
|
||||||
|
|
||||||
if g_cliMode:
|
if g_cliMode:
|
||||||
g_logger.info(f'Exit {USB_DEV_PRODUCT} or disconnect your console at any time to close this script.')
|
g_logger.info(SERVER_STOP_MSG)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -772,7 +810,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
if not g_cliMode or (g_cliMode and not g_nspTransferMode):
|
if not g_cliMode or (g_cliMode and not g_nspTransferMode):
|
||||||
g_logger.info(f'Receiving {file_type_str}: "{filename}".')
|
g_logger.info(f'Receiving {file_type_str}: "{filename}".')
|
||||||
|
|
||||||
# Perform validity checks.
|
# Perform sanity checks.
|
||||||
if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size):
|
if (not g_nspTransferMode) and file_size and (nsp_header_size >= file_size):
|
||||||
g_logger.error('NSP header size must be smaller than the full NSP size!\n')
|
g_logger.error('NSP header size must be smaller than the full NSP size!\n')
|
||||||
return USB_STATUS_MALFORMED_CMD
|
return USB_STATUS_MALFORMED_CMD
|
||||||
|
@ -795,7 +833,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
g_nspFilePath = ''
|
g_nspFilePath = ''
|
||||||
g_logger.debug('NSP transfer mode enabled!\n')
|
g_logger.debug('NSP transfer mode enabled!\n')
|
||||||
|
|
||||||
# Perform additional validity checks and get a file object to work with.
|
# Perform additional sanity checks and get a file object to work with.
|
||||||
if (not g_nspTransferMode) or (g_nspFile is None):
|
if (not g_nspTransferMode) or (g_nspFile is None):
|
||||||
# Generate full, absolute path to the destination file.
|
# Generate full, absolute path to the destination file.
|
||||||
fullpath = os.path.abspath(g_outputDir + os.path.sep + filename)
|
fullpath = os.path.abspath(g_outputDir + os.path.sep + filename)
|
||||||
|
@ -852,7 +890,7 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
usbSendStatus(USB_STATUS_SUCCESS)
|
usbSendStatus(USB_STATUS_SUCCESS)
|
||||||
|
|
||||||
# Start data transfer stage.
|
# Start data transfer stage.
|
||||||
g_logger.debug(f'Data transfer started. Saving {file_type_str} to: "{printable_fullpath}".')
|
g_logger.debug(f'Data transfer started. {"Saving" if file_type_str == "file" else "Writing"} {file_type_str} to: "{printable_fullpath}".')
|
||||||
|
|
||||||
offset = 0
|
offset = 0
|
||||||
blksize = USB_TRANSFER_BLOCK_SIZE
|
blksize = USB_TRANSFER_BLOCK_SIZE
|
||||||
|
@ -877,11 +915,8 @@ def usbHandleSendFileProperties(cmd_block: bytes) -> int | None:
|
||||||
pbar_n = g_nspHeaderSize
|
pbar_n = g_nspHeaderSize
|
||||||
pbar_file_size = g_nspSize
|
pbar_file_size = g_nspSize
|
||||||
|
|
||||||
# Get progress bar unit and unit divider. These will be used to display and calculate size values using a specific size unit (B, KiB, MiB, GiB).
|
|
||||||
(unit, unit_divider) = utilsGetSizeUnitAndDivisor(pbar_file_size)
|
|
||||||
|
|
||||||
# Display progress bar window.
|
# Display progress bar window.
|
||||||
g_progressBarWindow.start(pbar_file_size, pbar_n, unit_divider, prefix, unit)
|
g_progressBarWindow.start(pbar_file_size, pbar_n, prefix)
|
||||||
else:
|
else:
|
||||||
# Set current prefix (holds the filename for the current NSP file entry).
|
# Set current prefix (holds the filename for the current NSP file entry).
|
||||||
g_progressBarWindow.set_prefix(prefix)
|
g_progressBarWindow.set_prefix(prefix)
|
||||||
|
@ -970,7 +1005,11 @@ def usbHandleCancelFileTransfer(cmd_block: bytes) -> int:
|
||||||
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_START_SESSION:02X}) command.')
|
g_logger.debug(f'Received CancelFileTransfer ({USB_CMD_START_SESSION:02X}) command.')
|
||||||
|
|
||||||
if g_nspTransferMode:
|
if g_nspTransferMode:
|
||||||
|
if (g_nspSize > USB_TRANSFER_THRESHOLD) and (g_progressBarWindow is not None):
|
||||||
|
g_progressBarWindow.end()
|
||||||
|
|
||||||
utilsResetNspInfo(True)
|
utilsResetNspInfo(True)
|
||||||
|
|
||||||
g_logger.warning('Transfer cancelled.')
|
g_logger.warning('Transfer cancelled.')
|
||||||
return USB_STATUS_SUCCESS
|
return USB_STATUS_SUCCESS
|
||||||
else:
|
else:
|
||||||
|
@ -1344,7 +1383,7 @@ def uiInitialize() -> None:
|
||||||
console = LogConsole(g_tkScrolledTextLog)
|
console = LogConsole(g_tkScrolledTextLog)
|
||||||
|
|
||||||
# Initialize progress bar window object.
|
# Initialize progress bar window object.
|
||||||
bar_format = '{desc}\n\n{percentage:.2f}% - {n:.2f} / {total:.2f} {unit}\nElapsed time: {elapsed}. Remaining time: {remaining}.\nSpeed: {rate_fmt}.'
|
bar_format = '{desc}\n\n{percentage:.2f}% - {n:.2f} / {total:.2f} {unit}\nElapsed time: {elapsed}. Remaining time: {remaining}.\nSpeed: __custom_rate_fmt__.'
|
||||||
g_progressBarWindow = ProgressBarWindow(bar_format, g_tkRoot, 'File transfer', False, uiHandleExitProtocolStub)
|
g_progressBarWindow = ProgressBarWindow(bar_format, g_tkRoot, 'File transfer', False, uiHandleExitProtocolStub)
|
||||||
|
|
||||||
# Enter Tkinter main loop.
|
# Enter Tkinter main loop.
|
||||||
|
@ -1360,7 +1399,7 @@ def cliInitialize() -> None:
|
||||||
console = LogConsole()
|
console = LogConsole()
|
||||||
|
|
||||||
# Initialize progress bar window object.
|
# Initialize progress bar window object.
|
||||||
bar_format = '{percentage:.2f}% |{bar}| {n:.2f}/{total:.2f} [{elapsed}<{remaining}, {rate_fmt}]'
|
bar_format = '{percentage:.2f}% |{bar}| {n:.2f}/{total:.2f} {unit} [{elapsed}<{remaining}, __custom_rate_fmt__]'
|
||||||
g_progressBarWindow = ProgressBarWindow(bar_format)
|
g_progressBarWindow = ProgressBarWindow(bar_format)
|
||||||
|
|
||||||
# Print info.
|
# Print info.
|
||||||
|
|
Loading…
Reference in a new issue