diff --git a/client/securedrop_client/gui/conversation/export/export_wizard.py b/client/securedrop_client/gui/conversation/export/export_wizard.py index 941ab349e..de33aeadc 100644 --- a/client/securedrop_client/gui/conversation/export/export_wizard.py +++ b/client/securedrop_client/gui/conversation/export/export_wizard.py @@ -111,14 +111,14 @@ def _set_layout(self) -> None: ) def _set_pages(self) -> None: - for page in [ - self._create_preflight(), - self._create_errorpage(), - self._create_insert_usb(), - self._create_passphrase_prompt(), - self._create_done(), + for id, page in [ + (Pages.PREFLIGHT, self._create_preflight()), + (Pages.ERROR, self._create_errorpage()), + (Pages.INSERT_USB, self._create_insert_usb()), + (Pages.UNLOCK_USB, self._create_passphrase_prompt()), + (Pages.EXPORT_DONE, self._create_done()), ]: - self.addPage(page) + self.setPage(id, page) # Nice to have, but steals the focus from the password field after 1 character is typed. # Probably another way to have it be based on validating the status @@ -152,7 +152,7 @@ def on_status_received(self, status: ExportStatus) -> None: To update the text on an individual page, the page listens for this signal and can call `update_content` in the listener. """ - logger.debug(f"Wizard received {status.value}") + logger.debug(f"Wizard received {status.value}. Current page is {type(self.currentPage())}") # Unrecoverable - end the wizard if status in [ @@ -178,18 +178,9 @@ def on_status_received(self, status: ExportStatus) -> None: target = Pages.UNLOCK_USB # Someone may have yanked out or unmounted a USB - if target: + if target and self.currentId() > target: self.rewind(target) - # If the user is stuck, show them a hint. - should_show_hint = target == self.currentId() - logger.debug(f"{status.value} - should show hint is {should_show_hint}") - - page = self.currentPage() - # Sometimes self.currentPage() is None - if page and should_show_hint: - page.update_content(status, should_show_hint) - # Update status self.current_status = status @@ -207,8 +198,7 @@ def end_wizard_with_error(self, error: ExportStatus) -> None: """ if isinstance(self.currentPage(), PreflightPage): # Update its status so it shows error next self.currentPage() - # self.next() - logger.debug("On preflight page") + logger.debug("On preflight page, no reordering needed") else: while self.currentId() > Pages.ERROR: self.back() @@ -227,9 +217,5 @@ def _create_insert_usb(self) -> QWizardPage: def _create_passphrase_prompt(self) -> QWizardPage: return PassphraseWizardPage(self.export) - # def _create_export(self) -> QWizardPage: - # return ExportPage(self.export) - - # readywhen: all done def _create_done(self) -> QWizardPage: return FinalPage(self.export) diff --git a/client/securedrop_client/gui/conversation/export/export_wizard_page.py b/client/securedrop_client/gui/conversation/export/export_wizard_page.py index 1c5dc9b61..b6d9a3e05 100644 --- a/client/securedrop_client/gui/conversation/export/export_wizard_page.py +++ b/client/securedrop_client/gui/conversation/export/export_wizard_page.py @@ -173,8 +173,9 @@ def stop_animate_header(self) -> None: @pyqtSlot(object) def on_status_received(self, status: ExportStatus) -> None: logger.debug(f"Child page received status {status.value}") - self.status = status - self.completeChanged.emit() + # Child pages should overwrite this method + # self.status = status + # self.completeChanged.emit() # Some children (not the Prefight Page) may wish to call update_content here def update_content(self, status: ExportStatus, should_show_hint: bool = False) -> None: @@ -195,6 +196,7 @@ def update_content(self, status: ExportStatus, should_show_hint: bool = False) - class PreflightPage(ExportWizardPage): def __init__(self, export, summary): + self.summary = summary header = _( "Preparing to export:
" '{}' ).format(summary) @@ -241,10 +243,16 @@ def nextId(self): @pyqtSlot(object) def on_status_received(self, status: ExportStatus): self.stop_animate_header() - if status in (ExportStatus.DEVICE_LOCKED, ExportStatus.DEVICE_WRITABLE): + if status in ( + ExportStatus.DEVICE_LOCKED, + ExportStatus.DEVICE_WRITABLE, + ExportStatus.NO_DEVICE_DETECTED, + ExportStatus.MULTI_DEVICE_DETECTED, + ExportStatus.INVALID_DEVICE_DETECTED, + ): header = _( "Ready to export:
" '{}' - ).format(self.header_text) + ).format(self.summary) self.header.setText(header) self.status = status @@ -256,17 +264,13 @@ def __init__(self, export, summary): super().__init__(export, header=header, body=summary) - def on_status_received(self, status: ExportStatus): - body = STATUS_MESSAGES.get(status) - self.body.setText(body) - self.status = status - def isComplete(self) -> bool: return False class InsertUSBPage(ExportWizardPage): def __init__(self, export, summary): + self.summary = summary header = _("Ready to export:
" '{}').format( summary ) @@ -278,38 +282,39 @@ def __init__(self, export, summary): @pyqtSlot(object) def on_status_received(self, status: ExportStatus) -> None: - super().on_status_received(status) + logger.debug(f"InsertUSB received {status.value}") should_show_hint = status in ( ExportStatus.NO_DEVICE_DETECTED, ExportStatus.MULTI_DEVICE_DETECTED, ExportStatus.INVALID_DEVICE_DETECTED, ) self.update_content(status, should_show_hint) + self.status = status - def validatePage(self) -> bool: - """ - Override method to implement custom validation logic, which prevents the - wizard from advancing past this stage unless preconditions are met, and - shows an error-specific hint to the user. - """ - if self.status in (ExportStatus.DEVICE_WRITABLE, ExportStatus.DEVICE_LOCKED): - self.error_details.hide() - return True - else: - logger.debug(f"Status is {self.status}, rechecking") - - # Show the user a hint - if self.status in ( - ExportStatus.MULTI_DEVICE_DETECTED, - ExportStatus.NO_DEVICE_DETECTED, - ExportStatus.INVALID_DEVICE_DETECTED, - ): - self.update_content(self.status, should_show_hint=True) - else: - # Shouldn't reach - # Status may be None here - logger.warning("InsertUSBPage encountered unexpected status") - return False + # def validatePage(self) -> bool: + # """ + # Override method to implement custom validation logic, which prevents the + # wizard from advancing past this stage unless preconditions are met, and + # shows an error-specific hint to the user. + # """ + # if self.status in (ExportStatus.DEVICE_WRITABLE, ExportStatus.DEVICE_LOCKED): + # self.error_details.hide() + # return True + # else: + # logger.debug(f"Status is {self.status}") + + # # Show the user a hint + # if self.status in ( + # ExportStatus.MULTI_DEVICE_DETECTED, + # ExportStatus.NO_DEVICE_DETECTED, + # ExportStatus.INVALID_DEVICE_DETECTED, + # ): + # self.update_content(self.status, should_show_hint=True) + # else: + # # Shouldn't reach + # # Status may be None here + # logger.warning("InsertUSBPage encountered unexpected status") + # return False def nextId(self): """ @@ -318,8 +323,14 @@ def nextId(self): if self.status == ExportStatus.DEVICE_WRITABLE: logger.debug("Skip password prompt") return Pages.EXPORT_DONE + elif self.status == ExportStatus.DEVICE_LOCKED: + return Pages.UNLOCK_USB + elif self.status in (ExportStatus.UNEXPECTED_RETURN_STATUS, ExportStatus.DEVICE_ERROR): + return Pages.ERROR else: - return super().nextId() + next = super().nextId() + logger.error("Unexpected status on InsertUSBPage {status.value}, nextID is {next}") + return next class FinalPage(ExportWizardPage): @@ -332,10 +343,13 @@ def __init__(self, export: Export) -> None: @pyqtSlot(object) def on_status_received(self, status: ExportStatus) -> None: - super().on_status_received(status) + logger.debug(f"Final page received status {status}") self.update_content(status) + self.status = status def update_content(self, status: ExportStatus, should_show_hint: bool = False): + header = None + body = None if status == ExportStatus.SUCCESS_EXPORT: header = _("Export successful") body = _( @@ -347,13 +361,11 @@ def update_content(self, status: ExportStatus, should_show_hint: bool = False): body = STATUS_MESSAGES.get(ExportStatus.ERROR_EXPORT_CLEANUP) else: - header = _("Export failed") - if not self.status: - self.status = ExportStatus.UNEXPECTED_RETURN_STATUS - body = STATUS_MESSAGES.get(self.status) + header = _("Working...") self.header.setText(header) - self.body.setText(body) + if body: + self.body.setText(body) class PassphraseWizardPage(ExportWizardPage): @@ -398,15 +410,39 @@ def _build_layout(self) -> QVBoxLayout: passphrase_form_layout.addWidget(self.passphrase_field) passphrase_form_layout.addWidget(check, alignment=Qt.AlignRight) - # Add it layout.addWidget(self.passphrase_form) return layout @pyqtSlot(object) def on_status_received(self, status: ExportStatus) -> None: - super().on_status_received(status) + logger.debug(f"Passphrase page rececived {status.value}") should_show_hint = status in ( ExportStatus.ERROR_UNLOCK_LUKS, ExportStatus.ERROR_UNLOCK_GENERIC, ) self.update_content(status, should_show_hint) + self.status = status + + def validate(self): + return self.status == ExportStatus.DEVICE_WRITABLE + + def nextId(self): + if self.status == ExportStatus.SUCCESS_EXPORT: + return Pages.EXPORT_DONE + elif self.status in (ExportStatus.ERROR_UNLOCK_LUKS, ExportStatus.ERROR_UNLOCK_GENERIC): + return Pages.UNLOCK_USB + elif self.status in ( + ExportStatus.NO_DEVICE_DETECTED, + ExportStatus.MULTI_DEVICE_DETECTED, + ExportStatus.INVALID_DEVICE_DETECTED, + ): + return Pages.INSERT_USB + elif self.status in ( + ExportStatus.ERROR_MOUNT, + ExportStatus.ERROR_EXPORT, + ExportStatus.ERROR_EXPORT_CLEANUP, + ExportStatus.UNEXPECTED_RETURN_STATUS, + ): + return Pages.ERROR + else: + return super().nextId()