Skip to content

Commit

Permalink
(WIP) Simpler signaling logic in main wizard page. Basic working tran…
Browse files Browse the repository at this point in the history
…sitions.
  • Loading branch information
rocodes committed Feb 5, 2024
1 parent 22a968e commit c6ec546
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 67 deletions.
34 changes: 10 additions & 24 deletions client/securedrop_client/gui/conversation/export/export_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 [
Expand All @@ -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

Expand All @@ -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()
Expand All @@ -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)
122 changes: 79 additions & 43 deletions client/securedrop_client/gui/conversation/export/export_wizard_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:<br />" '<span style="font-weight:normal">{}</span>'
).format(summary)
Expand Down Expand Up @@ -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:<br />" '<span style="font-weight:normal">{}</span>'
).format(self.header_text)
).format(self.summary)
self.header.setText(header)
self.status = status

Expand All @@ -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:<br />" '<span style="font-weight:normal">{}</span>').format(
summary
)
Expand All @@ -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):
"""
Expand All @@ -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):
Expand All @@ -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 = _(
Expand All @@ -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):
Expand Down Expand Up @@ -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()

0 comments on commit c6ec546

Please sign in to comment.