def __init__(self, app, data, storage, payload, instclass): NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) self._ready = False self.selected_disks = self.data.ignoredisk.onlyuse[:] self.selection = None self.autopart = None self.clearPartType = None # This list gets set up once in initialize and should not be modified # except perhaps to add advanced devices. It will remain the full list # of disks that can be included in the install. self.disks = [] self.errors = [] self.warnings = [] if self.data.zerombr.zerombr and arch.isS390(): # if zerombr is specified in a ks file and there are unformatted # dasds, automatically format them to_format = make_unformatted_dasd_list(self.selected_disks) if to_format: self.run_dasdfmt(to_format) if not flags.automatedInstall: # default to using autopart for interactive installs self.data.autopart.autopart = True
def input(self, args, key): """Grab the disk choice and update things""" if key == "c": if self.selected_disks: # check selected disks to see if we have any unformatted DASDs # if we're on s390x, since they need to be formatted before we # can use them. if arch.isS390(): to_format = make_unformatted_dasd_list(self.selected_disks) if to_format: self.run_dasdfmt(to_format) return None newspoke = AutoPartSpoke(self.app, self.data, self.storage, self.payload, self.instclass) self.app.switch_screen_modal(newspoke) self.apply() self.execute() self.close() return None try: number = int(key) self._update_disk_list(self.disks[number - 1]) return None except (ValueError, KeyError, IndexError): return key
def input(self, args, key): """Grab the disk choice and update things""" try: keyid = int(key) - 1 self.selection = keyid if len(self.disks) > 1 and keyid == len(self.disks): self._select_all_disks() else: self._update_disk_list(self.disks[keyid]) return INPUT_PROCESSED except (ValueError, IndexError): if key.lower() == "c": if self.selected_disks: # check selected disks to see if we have any unformatted DASDs # if we're on s390x, since they need to be formatted before we # can use them. if arch.isS390(): to_format = make_unformatted_dasd_list(self.selected_disks) if to_format: self.run_dasdfmt(to_format) return None newspoke = AutoPartSpoke(self.app, self.data, self.storage, self.payload, self.instclass) self.app.switch_screen_modal(newspoke) self.apply() self.execute() self.close() return INPUT_PROCESSED else: return key
def run_dasdfmt(self): """ Though the same function exists in pyanaconda.ui.gui.spokes.lib.dasdfmt, this instance doesn't include any of the UI pieces and should only really be getting called on ks installations with "zerombr". """ # wait for the initial storage thread to complete before taking any new # actions on storage devices threadMgr.wait(constants.THREAD_STORAGE) to_format = make_unformatted_dasd_list(self.selected_disks) if not to_format: # nothing to do here; bail return hubQ.send_message(self.__class__.__name__, _("Formatting DASDs")) for disk in to_format: try: format_dasd(disk) except DasdFormatError as err: # Log errors if formatting fails, but don't halt the installer log.error(str(err)) continue # now re-initialize storage to pick up the newly formatted disks protectedNames = [d.name for d in self.storage.protectedDevices] storageInitialize(self.storage, self.data, protectedNames) # I really hate doing this, but the way is the way; probably the most # correct way to kajigger the storage spoke into becoming ready self.execute()
def run_dasdfmt(self): """ Though the same function exists in pyanaconda.ui.gui.spokes.lib.dasdfmt, this instance doesn't include any of the UI pieces and should only really be getting called on ks installations with "zerombr". """ # wait for the initial storage thread to complete before taking any new # actions on storage devices threadMgr.wait(constants.THREAD_STORAGE) to_format = make_unformatted_dasd_list(self.selected_disks) if not to_format: # nothing to do here; bail return hubQ.send_message(self.__class__.__name__, _("Formatting DASDs")) for disk in to_format: try: format_dasd(disk) except DasdFormatError as err: # Log errors if formatting fails, but don't halt the installer log.error(str(err)) continue # now re-initialize storage to pick up the newly formatted disks protectedNames = [d.name for d in self.storage.protectedDevices] storageInitialize(self.storage, self.data, protectedNames) # I really hate doing this, but the way is the way; probably the most # correct way to kajigger the storage spoke into becoming ready self.execute()
def input(self, args, key): """Grab the disk choice and update things""" self.errors = [] try: keyid = int(key) - 1 self.selection = keyid if len(self.disks) > 1 and keyid == len(self.disks): self._select_all_disks() else: self._update_disk_list(self.disks[keyid]) return INPUT_PROCESSED except (ValueError, IndexError): # TRANSLATORS: 'c' to continue if key.lower() == C_('TUI|Spoke Navigation', 'c'): if self.selected_disks: # check selected disks to see if we have any unformatted or # LDL DASDs if we're on s390x, since they need to be # formatted before we can use them. if arch.isS390(): unformatted = make_unformatted_dasd_list( self.selected_disks) ldl = [ d for d in self.selected_disks if is_ldl_dasd(d) ] # combine into one nice list dasds = list(set(unformatted + ldl)) if dasds: self.run_dasdfmt(dasds) return None # make sure no containers were split up by the user's disk # selection self.errors.extend( checkDiskSelection(self.storage, self.selected_disks)) if self.errors: # The disk selection has to make sense before we can # proceed. return None newspoke = AutoPartSpoke(self.app, self.data, self.storage, self.payload, self.instclass) self.app.switch_screen_modal(newspoke) self.apply() self.execute() self.close() return INPUT_PROCESSED else: return key
def run_dasdfmt(self): """ Though the same function exists in pyanaconda.ui.gui.spokes.lib.dasdfmt, this instance doesn't include any of the UI pieces and should only really be getting called on ks installations with "zerombr". """ # wait for the initial storage thread to complete before taking any new # actions on storage devices threadMgr.wait(constants.THREAD_STORAGE) to_format = make_unformatted_dasd_list(d.name for d in getDisks(self.storage.devicetree)) if not to_format: # nothing to do here; bail return hubQ.send_message(self.__class__.__name__, _("Formatting DASDs")) for disk in to_format: try: format_dasd(disk) except DasdFormatError as err: # Log errors if formatting fails, but don't halt the installer log.error(str(err)) continue
def run_dasdfmt(self): """ Though the same function exists in pyanaconda.ui.gui.spokes.lib.dasdfmt, this instance doesn't include any of the UI pieces and should only really be getting called on ks installations with "zerombr". """ # wait for the initial storage thread to complete before taking any new # actions on storage devices threadMgr.wait(constants.THREAD_STORAGE) to_format = make_unformatted_dasd_list( d.name for d in getDisks(self.storage.devicetree)) if not to_format: # nothing to do here; bail return hubQ.send_message(self.__class__.__name__, _("Formatting DASDs")) for disk in to_format: try: format_dasd(disk) except DasdFormatError as err: # Log errors if formatting fails, but don't halt the installer log.error(str(err)) continue
def on_back_clicked(self, button): # We can't exit early if it looks like nothing has changed because the # user might want to change settings presented in the dialogs shown from # within this method. # Remove all non-existing devices if autopart was active when we last # refreshed. if self._previous_autopart: self._previous_autopart = False for partition in self.storage.partitions[:]: # check if it's been removed in a previous iteration if not partition.exists and \ partition in self.storage.partitions: self.storage.recursiveRemove(partition) # hide/unhide disks as requested for disk in self.disks: if disk.name not in self.selected_disks and \ disk in self.storage.devices: self.storage.devicetree.hide(disk) elif disk.name in self.selected_disks and \ disk not in self.storage.devices: self.storage.devicetree.unhide(disk) # show the installation options dialog disks = [d for d in self.disks if d.name in self.selected_disks] disks_size = sum((d.size for d in disks), Size(bytes=0)) # No disks selected? The user wants to back out of the storage spoke. if not disks: NormalSpoke.on_back_clicked(self, button) return if arch.isS390(): # check for unformatted DASDs and launch dasdfmt if any discovered dasds = make_unformatted_dasd_list(self.selected_disks) if len(dasds) > 0: dialog = DasdFormatDialog(self.data, self.storage, dasds) ignoreEscape(dialog.window) rc = self.run_lightbox_dialog(dialog) if rc == 1: # User hit OK on the dialog, indicating they stayed on the # dialog until formatting completed; make sure we stay on # the storage spoke and don't return to the summary hub self.skipTo = "StorageSpoke" # we have to manaually call refresh so changes are picked up self.refresh() elif rc == 2: # User clicked uri to return to hub. NormalSpoke.on_back_clicked(self, button) return elif rc != 2: # User either hit cancel on the dialog or closed it via escape, # there was no formatting done. # NOTE: rc == 2 means the user clicked on the link that takes t # back to the hub. return # Figure out if the existing disk labels will work on this platform # you need to have at least one of the platform's labels in order for # any of the free space to be useful. disk_labels = set(disk.format.labelType for disk in disks if hasattr(disk.format, "labelType")) platform_labels = set(platform.diskLabelTypes) if disk_labels and platform_labels.isdisjoint(disk_labels): disk_free = 0 fs_free = 0 log.debug("Need disklabel: %s have: %s", ", ".join(platform_labels), ", ".join(disk_labels)) else: free_space = self.storage.getFreeSpace(disks=disks, clearPartType=CLEARPART_TYPE_NONE) disk_free = sum(f[0] for f in free_space.itervalues()) fs_free = sum(f[1] for f in free_space.itervalues()) required_space = self.payload.spaceRequired auto_swap = sum((r.size for r in self.storage.autoPartitionRequests if r.fstype == "swap"), Size(bytes=0)) log.debug("disk free: %s fs free: %s sw needs: %s auto swap: %s", disk_free, fs_free, required_space, auto_swap) if disk_free >= required_space + auto_swap: dialog = None elif disks_size >= required_space: if self._customPart.get_active() or self._reclaim.get_active(): dialog = None else: dialog = NeedSpaceDialog(self.data, payload=self.payload) dialog.refresh(required_space, auto_swap, disk_free, fs_free) rc = self.run_lightbox_dialog(dialog) else: dialog = NoSpaceDialog(self.data, payload=self.payload) dialog.refresh(required_space, auto_swap, disk_free, fs_free) rc = self.run_lightbox_dialog(dialog) if not dialog: # Plenty of room - there's no need to pop up a dialog, so just send # the user to wherever they asked to go. That's either the custom # spoke or the hub. # - OR - # Not enough room, but the user checked the reclaim button. self.encrypted = self._encrypted.get_active() if self._customPart.get_active(): self.autopart = False self.skipTo = "CustomPartitioningSpoke" else: self.autopart = True # We might first need to ask about an encryption passphrase. if not self._check_encrypted(): return # Oh and then we might also want to go to the reclaim dialog. if self._reclaim.get_active(): self.apply() if not self._show_resize_dialog(disks): # User pressed cancel on the reclaim dialog, so don't leave # the storage spoke. return elif rc == RESPONSE_CANCEL: # A cancel button was clicked on one of the dialogs. Stay on this # spoke. Generally, this is because the user wants to add more disks. return elif rc == RESPONSE_MODIFY_SW: # The "Fedora software selection" link was clicked on one of the # dialogs. Send the user to the software spoke. self.skipTo = "SoftwareSelectionSpoke" elif rc == RESPONSE_RECLAIM: # Not enough space, but the user can make enough if they do some # work and free up space. self.encrypted = self._encrypted.get_active() if not self._check_encrypted(): return self.apply() if not self._show_resize_dialog(disks): # User pressed cancel on the reclaim dialog, so don't leave # the storage spoke. return # And then go to the custom partitioning spoke if they chose to # do so. if self._customPart.get_active(): self.autopart = False self.skipTo = "CustomPartitioningSpoke" else: self.autopart = True elif rc == RESPONSE_QUIT: # Not enough space, and the user can't do anything about it so # they chose to quit. raise SystemExit("user-selected exit") else: # I don't know how we'd get here, but might as well have a # catch-all. Just stay on this spoke. return self.applyOnSkip = True NormalSpoke.on_back_clicked(self, button)
def run_dasdfmt(self, to_format=None): """ This generates the list of DASDs requiring dasdfmt and runs dasdfmt against them. to_format is an optional list of DASDs to format. This shouldn't be passed if run_dasdfmt is called during a ks installation, and if called during a manual installation, a list of DASDs needs to be passed. """ if not to_format: # go ahead and initialize this to_format = [] # if the storage thread is running, wait on it to complete before taking # any further actions on devices; most likely to occur if user has # zerombr in their ks file threadMgr.wait(THREAD_STORAGE) if flags.automatedInstall: # automated install case unformatted = [] ldl = [] if self.data.zerombr.zerombr: # unformatted DASDs unformatted += make_unformatted_dasd_list( [d.name for d in getDisks(self.storage.devicetree)]) if self.data.clearpart.cdl: # LDL DASDs ldl += [ d.name for d in self.storage.devicetree.dasd if is_ldl_dasd(d.name) ] # combine into one nice list to_format = list(set(unformatted + ldl)) else: # manual install; ask to verify they want to run dasdfmt # prepare our msg strings; copied directly from dasdfmt.glade summary = _( "The following unformatted or LDL DASDs have been " "detected on your system. You can choose to format them " "now with dasdfmt or cancel to leave them unformatted. " "Unformatted DASDs cannot be used during installation.\n\n") warntext = _( "Warning: All storage changes made using the installer will be lost when you choose to format.\n\nProceed to run dasdfmt?\n" ) displaytext = summary + "\n".join( "/dev/" + d for d in to_format) + "\n" + warntext # now show actual prompt; note -- in cmdline mode, auto-answer for # this is 'no', so unformatted and ldl DASDs will remain so unless # zerombr or cdl are added to the ks file question_window = YesNoDialog(self._app, displaytext) self._app.switch_screen_modal(question_window) if not question_window.answer: # no? well fine then, back to the storage spoke with you; return None for disk in to_format: try: print(_("Formatting /dev/%s. This may take a moment.") % disk) format_dasd(disk) except DasdFormatError as err: # Log errors if formatting fails, but don't halt the installer log.error("dasdfmt /dev/%s failed: %s", disk, err) continue # need to make devicetree aware of disk changes self.storage.devicetree.populate() if not flags.automatedInstall: # reinit storage threadMgr.add( AnacondaThread( name=THREAD_STORAGE, target=storageInitialize, args=(self.storage, self.data, self.storage.devicetree.protectedDevNames))) # update the summary screen with the changes self._initialize()
def on_back_clicked(self, button): # We can't exit early if it looks like nothing has changed because the # user might want to change settings presented in the dialogs shown from # within this method. # Remove all non-existing devices if autopart was active when we last # refreshed. if self._previous_autopart: self._previous_autopart = False for partition in self.storage.partitions[:]: # check if it's been removed in a previous iteration if not partition.exists and \ partition in self.storage.partitions: self.storage.recursiveRemove(partition) # hide/unhide disks as requested for disk in self.disks: if disk.name not in self.selected_disks and \ disk in self.storage.devices: self.storage.devicetree.hide(disk) elif disk.name in self.selected_disks and \ disk not in self.storage.devices: self.storage.devicetree.unhide(disk) # show the installation options dialog disks = [d for d in self.disks if d.name in self.selected_disks] disks_size = sum(Size(spec="%f MB" % d.size) for d in disks) # No disks selected? The user wants to back out of the storage spoke. if not disks: NormalSpoke.on_back_clicked(self, button) return if arch.isS390(): # check for unformatted DASDs and launch dasdfmt if any discovered dasds = make_unformatted_dasd_list(self.selected_disks) if len(dasds) > 0: dialog = DasdFormatDialog(self.data, self.storage, dasds) ignoreEscape(dialog.window) rc = self.run_lightbox_dialog(dialog) if rc == 1: # User hit OK on the dialog, indicating they stayed on the # dialog until formatting completed; make sure we stay on # the storage spoke and don't return to the summary hub self.skipTo = "StorageSpoke" # we have to manaually call refresh so changes are picked up self.refresh() elif rc == 2: # User clicked uri to return to hub. NormalSpoke.on_back_clicked(self, button) return elif rc != 2: # User either hit cancel on the dialog or closed it via escape, so # there was no formatting done. # NOTE: rc == 2 means the user clicked on the link that takes them # back to the hub. return # Figure out if the existing disk labels will work on this platform # you need to have at least one of the platform's labels in order for # any of the free space to be useful. disk_labels = set(disk.format.labelType for disk in disks if hasattr(disk.format, "labelType")) platform_labels = set(platform.diskLabelTypes) if disk_labels and platform_labels.isdisjoint(disk_labels): disk_free = 0 fs_free = 0 log.debug("Need disklabel: %s have: %s" % (", ".join(platform_labels), ", ".join(disk_labels))) else: free_space = self.storage.getFreeSpace( disks=disks, clearPartType=CLEARPART_TYPE_NONE) disk_free = sum(f[0] for f in free_space.itervalues()) fs_free = sum(f[1] for f in free_space.itervalues()) required_space = self.payload.spaceRequired auto_swap = Size(bytes=0) for autoreq in self.storage.autoPartitionRequests: if autoreq.fstype == "swap": auto_swap += Size(spec="%d MB" % autoreq.size) log.debug("disk free: %s fs free: %s sw needs: %s auto swap: %s" % (disk_free, fs_free, required_space, auto_swap)) if disk_free >= required_space + auto_swap: dialog = None elif disks_size >= required_space: if self._customPart.get_active() or self._reclaim.get_active(): dialog = None else: dialog = InstallOptions1Dialog(self.data, payload=self.payload) dialog.refresh(required_space, auto_swap, disk_free, fs_free) rc = self.run_lightbox_dialog(dialog) else: dialog = InstallOptions2Dialog(self.data, payload=self.payload) dialog.refresh(required_space, auto_swap, disk_free, fs_free) rc = self.run_lightbox_dialog(dialog) if not dialog: # Plenty of room - there's no need to pop up a dialog, so just send # the user to wherever they asked to go. That's either the custom # spoke or the hub. # - OR - # Not enough room, but the user checked the reclaim button. self.encrypted = self._encrypted.get_active() if self._customPart.get_active(): self.autopart = False self.skipTo = "CustomPartitioningSpoke" else: self.autopart = True # We might first need to ask about an encryption passphrase. if not self._check_encrypted(): return # Oh and then we might also want to go to the reclaim dialog. if self._reclaim.get_active(): self.apply() if not self._show_resize_dialog(disks): # User pressed cancel on the reclaim dialog, so don't leave # the storage spoke. return elif rc == RESPONSE_CANCEL: # A cancel button was clicked on one of the dialogs. Stay on this # spoke. Generally, this is because the user wants to add more disks. return elif rc == RESPONSE_MODIFY_SW: # The "Fedora software selection" link was clicked on one of the # dialogs. Send the user to the software spoke. self.skipTo = "SoftwareSelectionSpoke" elif rc == RESPONSE_RECLAIM: # Not enough space, but the user can make enough if they do some # work and free up space. self.encrypted = self._encrypted.get_active() if not self._check_encrypted(): return self.apply() if not self._show_resize_dialog(disks): # User pressed cancel on the reclaim dialog, so don't leave # the storage spoke. return # And then go to the custom partitioning spoke if they chose to # do so. if self._customPart.get_active(): self.autopart = False self.skipTo = "CustomPartitioningSpoke" else: self.autopart = True elif rc == RESPONSE_QUIT: # Not enough space, and the user can't do anything about it so # they chose to quit. raise SystemExit("user-selected exit") else: # I don't know how we'd get here, but might as well have a # catch-all. Just stay on this spoke. return self.applyOnSkip = True NormalSpoke.on_back_clicked(self, button)
def on_back_clicked(self, button): # We can't exit early if it looks like nothing has changed because the # user might want to change settings presented in the dialogs shown from # within this method. # Do not enter this method multiple times if user clicking multiple times # on back button if self._back_clicked: return else: self._back_clicked = True # Remove all non-existing devices if autopart was active when we last # refreshed. if self._previous_autopart: self._previous_autopart = False for partition in self.storage.partitions[:]: # check if it's been removed in a previous iteration if not partition.exists and \ partition in self.storage.partitions: self.storage.recursiveRemove(partition) # make sure no containers were split up by the user's disk selection self.clear_info() self.errors = checkDiskSelection(self.storage, self.selected_disks) if self.errors: # The disk selection has to make sense before we can proceed. self.set_error( _("There was a problem with your disk selection. " "Click here for details.")) self._back_clicked = False return # hide/unhide disks as requested for disk in self.disks: if disk.name not in self.selected_disks and \ disk in self.storage.devices: self.storage.devicetree.hide(disk) elif disk.name in self.selected_disks and \ disk not in self.storage.devices: self.storage.devicetree.unhide(disk) # show the installation options dialog disks = [d for d in self.disks if d.name in self.selected_disks] disks_size = sum((d.size for d in disks), Size(0)) # No disks selected? The user wants to back out of the storage spoke. if not disks: NormalSpoke.on_back_clicked(self, button) return if arch.isS390(): # check for unformatted DASDs and launch dasdfmt if any discovered dasds = make_unformatted_dasd_list(self.selected_disks) if len(dasds) > 0: # We want to apply current selection before running dasdfmt to # prevent this information from being lost afterward applyDiskSelection(self.storage, self.data, self.selected_disks) dialog = DasdFormatDialog(self.data, self.storage, dasds) ignoreEscape(dialog.window) rc = self.run_lightbox_dialog(dialog) if rc == 1: # User hit OK on the dialog self.refresh() elif rc == 2: # User clicked uri to return to hub. NormalSpoke.on_back_clicked(self, button) return elif rc != 2: # User either hit cancel on the dialog or closed it via escape, # there was no formatting done. # NOTE: rc == 2 means the user clicked on the link that takes t # back to the hub. self._back_clicked = False return # Figure out if the existing disk labels will work on this platform # you need to have at least one of the platform's labels in order for # any of the free space to be useful. disk_labels = set(disk.format.labelType for disk in disks if hasattr(disk.format, "labelType")) platform_labels = set(platform.diskLabelTypes) if disk_labels and platform_labels.isdisjoint(disk_labels): disk_free = 0 fs_free = 0 log.debug("Need disklabel: %s have: %s", ", ".join(platform_labels), ", ".join(disk_labels)) else: free_space = self.storage.getFreeSpace( disks=disks, clearPartType=CLEARPART_TYPE_NONE) disk_free = sum(f[0] for f in free_space.values()) fs_free = sum(f[1] for f in free_space.values()) required_space = self.payload.spaceRequired auto_swap = sum((r.size for r in self.storage.autoPartitionRequests if r.fstype == "swap"), Size(0)) if self.autopart and auto_swap == Size(0): # autopartitioning requested, but not applied yet (=> no auto swap # requests), ask user for enough space to fit in the suggested swap auto_swap = autopart.swapSuggestion() log.debug("disk free: %s fs free: %s sw needs: %s auto swap: %s", disk_free, fs_free, required_space, auto_swap) if disk_free >= required_space + auto_swap: dialog = None elif disks_size >= required_space: if self._customPart.get_active() or self._reclaim.get_active(): dialog = None else: dialog = NeedSpaceDialog(self.data, payload=self.payload) dialog.refresh(required_space, auto_swap, disk_free, fs_free) rc = self.run_lightbox_dialog(dialog) else: dialog = NoSpaceDialog(self.data, payload=self.payload) dialog.refresh(required_space, auto_swap, disk_free, fs_free) rc = self.run_lightbox_dialog(dialog) if not dialog: # Plenty of room - there's no need to pop up a dialog, so just send # the user to wherever they asked to go. That's either the custom # spoke or the hub. # - OR - # Not enough room, but the user checked the reclaim button. self.encrypted = self._encrypted.get_active() if self._customPart.get_active(): self.autopart = False self.skipTo = "CustomPartitioningSpoke" else: self.autopart = True # We might first need to ask about an encryption passphrase. if not self._check_encrypted(): self._back_clicked = False return # Oh and then we might also want to go to the reclaim dialog. if self._reclaim.get_active(): self.apply() if not self._show_resize_dialog(disks): # User pressed cancel on the reclaim dialog, so don't leave # the storage spoke. self._back_clicked = False return elif rc == RESPONSE_CANCEL: # A cancel button was clicked on one of the dialogs. Stay on this # spoke. Generally, this is because the user wants to add more disks. self._back_clicked = False return elif rc == RESPONSE_MODIFY_SW: # The "Fedora software selection" link was clicked on one of the # dialogs. Send the user to the software spoke. self.skipTo = "SoftwareSelectionSpoke" elif rc == RESPONSE_RECLAIM: # Not enough space, but the user can make enough if they do some # work and free up space. self.encrypted = self._encrypted.get_active() if not self._check_encrypted(): return self.apply() if not self._show_resize_dialog(disks): # User pressed cancel on the reclaim dialog, so don't leave # the storage spoke. self._back_clicked = False return # And then go to the custom partitioning spoke if they chose to # do so. if self._customPart.get_active(): self.autopart = False self.skipTo = "CustomPartitioningSpoke" else: self.autopart = True elif rc == RESPONSE_QUIT: # Not enough space, and the user can't do anything about it so # they chose to quit. raise SystemExit("user-selected exit") else: # I don't know how we'd get here, but might as well have a # catch-all. Just stay on this spoke. self._back_clicked = False return if self.autopart: refreshAutoSwapSize(self.storage) self.applyOnSkip = True NormalSpoke.on_back_clicked(self, button)