class FilterSpoke(NormalSpoke): builderObjects = [ "diskStore", "filterWindow", "searchModel", "multipathModel", "otherModel", "raidModel", "zModel" ] mainWidgetName = "filterWindow" uiFile = "spokes/filter.glade" category = SystemCategory title = N_("_INSTALLATION DESTINATION") def __init__(self, *args): NormalSpoke.__init__(self, *args) self.applyOnSkip = True @property def indirect(self): return True def apply(self): onlyuse = self.selected_disks[:] for disk in [d for d in self.storage.disks if d.name in onlyuse]: onlyuse.extend( [d.name for d in disk.ancestors if d.name not in onlyuse]) self.data.ignoredisk.onlyuse = onlyuse self.data.clearpart.drives = self.selected_disks[:] def initialize(self): NormalSpoke.initialize(self) self.pages = [ SearchPage(self.storage, self.builder), MultipathPage(self.storage, self.builder), OtherPage(self.storage, self.builder), RaidPage(self.storage, self.builder), ZPage(self.storage, self.builder) ] self._notebook = self.builder.get_object("advancedNotebook") if not arch.isS390(): self._notebook.remove_page(-1) self.builder.get_object("addZFCPButton").destroy() if not has_fcoe(): self.builder.get_object("addFCOEButton").destroy() self._store = self.builder.get_object("diskStore") self._addDisksButton = self.builder.get_object("addDisksButton") def _real_ancestors(self, disk): # Return a list of all the ancestors of a disk, but remove the disk # itself from this list. return [d for d in disk.ancestors if d.name != disk.name] def refresh(self): NormalSpoke.refresh(self) self.disks = getDisks(self.storage.devicetree) self.selected_disks = self.data.ignoredisk.onlyuse[:] self.ancestors = itertools.chain( *map(self._real_ancestors, self.disks)) self.ancestors = map(lambda d: d.name, self.ancestors) self._store.clear() allDisks = [] multipathDisks = [] otherDisks = [] raidDisks = [] zDisks = [] # Now all all the non-local disks to the store. Everything has been set up # ahead of time, so there's no need to configure anything. We first make # these lists of disks, then call setup on each individual page. This is # because there could be page-specific setup to do that requires a complete # view of all the disks on that page. for disk in self.disks: if self.pages[1].ismember(disk): multipathDisks.append(disk) elif self.pages[2].ismember(disk): otherDisks.append(disk) elif self.pages[3].ismember(disk): raidDisks.append(disk) elif self.pages[4].ismember(disk): zDisks.append(disk) allDisks.append(disk) self.pages[0].setup(self._store, self.selected_disks, allDisks) self.pages[1].setup(self._store, self.selected_disks, multipathDisks) self.pages[2].setup(self._store, self.selected_disks, otherDisks) self.pages[3].setup(self._store, self.selected_disks, raidDisks) self.pages[4].setup(self._store, self.selected_disks, zDisks) self._update_summary() def _update_summary(self): summaryButton = self.builder.get_object("summary_button") label = summaryButton.get_children()[0] # We need to remove ancestor devices from the count. Otherwise, we'll # end up in a situation where selecting one multipath device could # potentially show three devices selected (mpatha, sda, sdb for instance). count = len([ disk for disk in self.selected_disks if disk not in self.ancestors ]) summary = P_("%d _storage device selected", "%d _storage devices selected", count) % count label.set_use_markup(True) label.set_markup("<span foreground='blue'><u>%s</u></span>" % summary) label.set_use_underline(True) summaryButton.set_visible(count > 0) label.set_sensitive(count > 0) def on_back_clicked(self, button): self.skipTo = "StorageSpoke" NormalSpoke.on_back_clicked(self, button) def on_summary_clicked(self, button): dialog = SelectedDisksDialog(self.data) # Include any disks selected in the initial storage spoke, plus any # selected in this filter UI. disks = [ disk for disk in self.disks if disk.name in self.selected_disks ] free_space = self.storage.getFreeSpace(disks=disks) with enlightbox(self.window, dialog.window): dialog.refresh(disks, free_space, showRemove=False, setBoot=False) dialog.run() def on_find_clicked(self, button): n = self._notebook.get_current_page() self.pages[n].filterActive = True self.pages[n].model.refilter() def on_clear_icon_clicked(self, entry, icon_pos, event): if icon_pos == Gtk.EntryIconPosition.SECONDARY: entry.set_text("") def on_page_switched(self, notebook, newPage, newPageNum, *args): self.pages[newPageNum].model.refilter() notebook.get_nth_page(newPageNum).show_all() def on_row_toggled(self, button, path): if not path: return page_index = self._notebook.get_current_page() filter_model = self.pages[page_index].model model_itr = filter_model.get_iter(path) itr = filter_model.convert_iter_to_child_iter(model_itr) self._store[itr][1] = not self._store[itr][1] if self._store[itr][1] and self._store[itr][ 3] not in self.selected_disks: self.selected_disks.append(self._store[itr][3]) elif not self._store[itr][1] and self._store[itr][ 3] in self.selected_disks: self.selected_disks.remove(self._store[itr][3]) self._update_summary() def on_add_iscsi_clicked(self, widget, *args): dialog = ISCSIDialog(self.data, self.storage) with enlightbox(self.window, dialog.window): dialog.refresh() dialog.run() # We now need to refresh so any new disks picked up by adding advanced # storage are displayed in the UI. self.refresh() def on_add_fcoe_clicked(self, widget, *args): dialog = FCoEDialog(self.data, self.storage) with enlightbox(self.window, dialog.window): dialog.refresh() dialog.run() # We now need to refresh so any new disks picked up by adding advanced # storage are displayed in the UI. self.refresh() def on_add_zfcp_clicked(self, widget, *args): dialog = ZFCPDialog(self.data, self.storage) with enlightbox(self.window, dialog.window): dialog.refresh() dialog.run() # We now need to refresh so any new disks picked up by adding advanced # storage are displayed in the UI. self.refresh() ## ## SEARCH TAB SIGNAL HANDLERS ## def on_search_type_changed(self, combo): ndx = combo.get_active() notebook = self.builder.get_object("searchTypeNotebook") findButton = self.builder.get_object("searchFindButton") findButton.set_sensitive(ndx != 0) notebook.set_current_page(ndx) ## ## MULTIPATH TAB SIGNAL HANDLERS ## def on_multipath_type_changed(self, combo): ndx = combo.get_active() notebook = self.builder.get_object("multipathTypeNotebook") findButton = self.builder.get_object("multipathFindButton") findButton.set_sensitive(ndx != 0) notebook.set_current_page(ndx) ## ## OTHER TAB SIGNAL HANDLERS ## def on_other_type_combo_changed(self, combo): ndx = combo.get_active() notebook = self.builder.get_object("otherTypeNotebook") findButton = self.builder.get_object("otherFindButton") findButton.set_sensitive(ndx != 0) notebook.set_current_page(ndx) ## ## Z TAB SIGNAL HANDLERS ## def on_z_type_combo_changed(self, combo): ndx = combo.get_active() notebook = self.builder.get_object("zTypeNotebook") findButton = self.builder.get_object("zFindButton") findButton.set_sensitive(ndx != 0) notebook.set_current_page(ndx)
class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke): """ .. inheritance-diagram:: UserSpoke :parts: 3 """ title = N_("User creation") category = UserSettingsCategory edit_fields = [ Entry("Create user", "_create", EditTUISpoke.CHECK, True), Entry("Fullname", "gecos", GECOS_VALID, lambda self, args: args._create), Entry("Username", "name", check_username, lambda self, args: args._create), Entry("Use password", "_use_password", EditTUISpoke.CHECK, lambda self, args: args._create), Entry("Password", "_password", EditTUISpoke.PASSWORD, lambda self, args: args._use_password and args._create), Entry("Administrator", "_admin", EditTUISpoke.CHECK, lambda self, args: args._create), Entry("Groups", "_groups", GROUPLIST_SIMPLE_VALID, lambda self, args: args._create) ] @classmethod def should_run(cls, environment, data): # the user spoke should run always in the anaconda and in firstboot only # when doing reconfig or if no user has been created in the installation if environment == ANACONDA_ENVIRON: return True elif environment == FIRSTBOOT_ENVIRON and data is None: # cannot decide, stay in the game and let another call with data # available (will come) decide return True elif environment == FIRSTBOOT_ENVIRON and data and \ (data.firstboot.firstboot == FIRSTBOOT_RECONFIG or \ len(data.user.userList) == 0): return True else: return False def __init__(self, app, data, storage, payload, instclass): FirstbootSpokeMixIn.__init__(self) EditTUISpoke.__init__(self, app, data, storage, payload, instclass, "user") if self.data.user.userList: self.args = self.data.user.userList[0] self.args._create = True else: self.args = self.data.UserData() self.args._create = False self.args._use_password = self.args.isCrypted or self.args.password # Keep the password separate from the kickstart data until apply() # so that all of the properties are set at once self.args._password = "" self.errors = [] def refresh(self, args=None): self.args._admin = "wheel" in self.args.groups self.args._groups = ", ".join(self.args.groups) # if we have any errors, display them while self.errors: print(self.errors.pop()) return EditTUISpoke.refresh(self, args) @property def completed(self): """ Verify a user is created; verify pw is set if option checked. """ if len(self.data.user.userList) > 0: if self.args._use_password and not bool(self.args.password or self.args.isCrypted): return False else: return True else: return False @property def showable(self): return not (self.completed and flags.automatedInstall and self.data.user.seen and not self.dialog.policy.changesok) @property def mandatory(self): """ Only mandatory if the root pw hasn't been set in the UI eg. not mandatory if the root account was locked in a kickstart """ return not self.data.rootpw.password and not self.data.rootpw.lock @property def status(self): if len(self.data.user.userList) == 0: return _("No user will be created") elif self.args._use_password and not bool(self.args.password or self.args.isCrypted): return _("You must set a password") elif "wheel" in self.data.user.userList[0].groups: return _("Administrator %s will be created" ) % self.data.user.userList[0].name else: return _( "User %s will be created") % self.data.user.userList[0].name def input(self, args, key): self.dialog.wrong_input_message = None try: field = self.visible_fields[int(key) - 1] except (ValueError, IndexError): pass else: if field.attribute == "gecos": self.dialog.wrong_input_message = _( "Full name can't contain the ':' character") elif field.attribute == "name": # more granular message is returned by check_username pass elif field.attribute == "_groups": self.dialog.wrong_input_message = _( "Either a group name in the group list is invalid or groups are not separated by a comma" ) return EditTUISpoke.input(self, args, key) def apply(self): if self.args.gecos and not self.args.name: username = guess_username(self.args.gecos) valid, msg = check_username(username) if not valid: self.errors.append( _("Invalid user name: %(name)s.\n%(error_message)s") % { "name": username, "error_message": msg }) else: self.args.name = guess_username(self.args.gecos) self.args.groups = [ g.strip() for g in self.args._groups.split(",") if g ] # Add or remove the user from wheel group if self.args._admin and "wheel" not in self.args.groups: self.args.groups.append("wheel") elif not self.args._admin and "wheel" in self.args.groups: self.args.groups.remove("wheel") # Add or remove the user from userlist as needed if self.args._create and (self.args not in self.data.user.userList and self.args.name): self.data.user.userList.append(self.args) elif (not self.args._create) and (self.args in self.data.user.userList): self.data.user.userList.remove(self.args) # encrypt and store password only if user entered anything; this should # preserve passwords set via kickstart if self.args._use_password and len(self.args._password) > 0: self.args.password = self.args._password self.args.isCrypted = True self.args.password_kickstarted = False # clear pw when user unselects to use pw else: self.args.password = "" self.args.isCrypted = False self.args.password_kickstarted = False
from blivet.devicefactory import SIZE_POLICY_MAX from blivet.devicefactory import DEVICE_TYPE_LVM from blivet.devicefactory import DEVICE_TYPE_BTRFS from blivet.devicefactory import DEVICE_TYPE_LVM_THINP from blivet.devicefactory import DEVICE_TYPE_MD from blivet.devicefactory import get_supported_raid_levels from blivet.devicelibs import btrfs from blivet.devicelibs import mdraid from blivet.devicelibs import raid import logging log = logging.getLogger("anaconda") RAID_NOT_ENOUGH_DISKS = N_("The RAID level you have selected (%(level)s) " "requires more disks (%(min)d) than you " "currently have selected (%(count)d).") CONTAINER_DIALOG_TITLE = N_("CONFIGURE %(container_type)s") CONTAINER_DIALOG_TEXT = N_("Please create a name for this %(container_type)s " "and select at least one disk below.") ContainerType = namedtuple("ContainerType", ["name", "label"]) CONTAINER_TYPES = { DEVICE_TYPE_LVM: ContainerType( N_("Volume Group"), CN_("GUI|Custom Partitioning|Configure|Devices", "_Volume Group:")), DEVICE_TYPE_LVM_THINP: ContainerType(
def get_container_type(device_type): return CONTAINER_TYPES.get( device_type, ContainerType( N_("container"), CN_("GUI|Custom Partitioning|Configure|Devices", "container")))
class ProgressSpoke(StandaloneTUISpoke): title = N_("Progress") postForHub = SummaryHub priority = 0 def __init__(self, app, ksdata, storage, payload, instclass): StandaloneTUISpoke.__init__(self, app, ksdata, storage, payload, instclass) self._stepped = False @property def completed(self): # this spoke is never completed, initially return False def _update_progress(self): """Handle progress updates from install thread.""" from pyanaconda.progress import progressQ import Queue q = progressQ.q # Grab all messages may have appeared since last time this method ran. while True: # Attempt to get a message out of the queue for how we should update # the progress bar. If there's no message, don't error out. # Also flush the communication Queue at least once a second and # process it's events so we can react to async evens (like a thread # throwing an exception) while True: try: (code, args) = q.get(timeout=1) break except Queue.Empty: pass finally: self.app.process_events() if code == progressQ.PROGRESS_CODE_INIT: # Text mode doesn't have a finite progress bar pass elif code == progressQ.PROGRESS_CODE_STEP: # Instead of updating a progress bar, we just print a pip # but print it without a new line. sys.stdout.write('.') sys.stdout.flush() # Use _stepped as an indication to if we need a newline before # the next message self._stepped = True elif code == progressQ.PROGRESS_CODE_MESSAGE: # This should already be translated if self._stepped: # Get a new line in case we've done a step before self._stepped = False print('') print(args[0]) elif code == progressQ.PROGRESS_CODE_COMPLETE: # There shouldn't be any more progress updates, so return q.task_done() if self._stepped: print('') return True elif code == progressQ.PROGRESS_CODE_QUIT: sys.exit(args[0]) q.task_done() return True def refresh(self, args=None): from pyanaconda.install import doInstall, doConfiguration from pyanaconda.threads import threadMgr, AnacondaThread # We print this here because we don't really use the window object print(_(self.title)) threadMgr.add( AnacondaThread(name=THREAD_INSTALL, target=doInstall, args=(self.storage, self.payload, self.data, self.instclass))) # This will run until we're all done with the install thread. self._update_progress() threadMgr.add( AnacondaThread(name=THREAD_CONFIGURATION, target=doConfiguration, args=(self.storage, self.payload, self.data, self.instclass))) # This will run until we're all done with the configuration thread. self._update_progress() iutil.ipmi_report(IPMI_FINISHED) # kickstart install, continue automatically if reboot or shutdown selected if flags.automatedInstall and self.data.reboot.action in [ KS_REBOOT, KS_SHUTDOWN ]: # Just pretend like we got input, and our input doesn't care # what it gets, it just quits. self.input(None, None) return True def prompt(self, args=None): return (_("\tInstallation complete. Press return to quit")) def input(self, args, key): # There is nothing to do here, just raise to exit the spoke raise ExitAllMainLoops() # Override Spoke.apply def apply(self): pass
from pyanaconda.i18n import _, N_, CN_ from pyanaconda.constants import DEFAULT_KEYBOARD, THREAD_KEYBOARD_INIT, THREAD_ADD_LAYOUTS_INIT from pyanaconda.ui.communication import hubQ from pyanaconda.iutil import strip_accents from pyanaconda.threads import threadMgr, AnacondaThread from pyanaconda.iutil import have_word_match import locale as locale_mod import logging log = logging.getLogger("anaconda") __all__ = ["KeyboardSpoke"] # %s will be replaced by key combination like Alt+Shift LAYOUT_SWITCHING_INFO = N_("%s to switch layouts.") ADD_LAYOUTS_INITIALIZE_THREAD = "AnaAddLayoutsInitializeThread" def _show_layout(column, renderer, model, itr, wrapper): return wrapper.get_layout_variant_description(model[itr][0]) def _show_description(column, renderer, model, itr, wrapper): value = wrapper.get_switch_opt_description(model[itr][0]) if model[itr][1]: value = "<b>%s</b>" % escape_markup(value) return value
class SourceSpoke(EditTUISpoke, SourceSwitchHandler): """ Spoke used to customize the install source repo. .. inheritance-diagram:: SourceSpoke :parts: 3 """ title = N_("Installation source") category = SoftwareCategory _protocols = (N_("Closest mirror"), "http://", "https://", "ftp://", "nfs") # default to 'closest mirror', as done in the GUI _selection = 1 def __init__(self, app, data, storage, payload, instclass): EditTUISpoke.__init__(self, app, data, storage, payload, instclass) SourceSwitchHandler.__init__(self) self._ready = False self._error = False self._cdrom = None def initialize(self): EditTUISpoke.initialize(self) threadMgr.add( AnacondaThread(name=THREAD_SOURCE_WATCHER, target=self._initialize)) payloadMgr.addListener(payloadMgr.STATE_ERROR, self._payload_error) def _initialize(self): """ Private initialize. """ threadMgr.wait(THREAD_PAYLOAD) # If we've previously set up to use a CD/DVD method, the media has # already been mounted by payload.setup. We can't try to mount it # again. So just use what we already know to create the selector. # Otherwise, check to see if there's anything available. if self.data.method.method == "cdrom": self._cdrom = self.payload.install_device elif not flags.automatedInstall: self._cdrom = opticalInstallMedia(self.storage.devicetree) self._ready = True def _payload_error(self): self._error = True def _repo_status(self): """ Return a string describing repo url or lack of one. """ if self.data.method.method == "url": return self.data.method.url or self.data.method.mirrorlist elif self.data.method.method == "nfs": return _("NFS server %s") % self.data.method.server elif self.data.method.method == "cdrom": return _("Local media") elif self.data.method.method == "harddrive": if not self.data.method.dir: return _("Error setting up software source") return os.path.basename(self.data.method.dir) elif self.payload.baseRepo: return _("Closest mirror") else: return _("Nothing selected") @property def showable(self): return isinstance(self.payload, PackagePayload) @property def status(self): if self._error: return _("Error setting up software source") elif not self.ready: return _("Processing...") else: return self._repo_status() @property def completed(self): if flags.automatedInstall and self.ready and not self.payload.baseRepo: return False else: return not self._error and self.ready and ( self.data.method.method or self.payload.baseRepo) def refresh(self, args=None): EditTUISpoke.refresh(self, args) threadMgr.wait(THREAD_PAYLOAD) _methods = [_("CD/DVD"), _("local ISO file"), _("Network")] if self.data.method.method == "harddrive" and \ get_mount_device(DRACUT_ISODIR) == get_mount_device(DRACUT_REPODIR): message = _( "The installation source is in use by the installer and cannot be changed." ) self._window += [TextWidget(message), ""] return True if args == 3: text = [TextWidget(_(p)) for p in self._protocols] else: self._window += [ TextWidget(_("Choose an installation source type.")) ] text = [TextWidget(m) for m in _methods] def _prep(i, w): """ Mangle our text to make it look pretty on screen. """ number = TextWidget("%2d)" % (i + 1)) return ColumnWidget([(4, [number]), (None, [w])], 1) # gnarl and mangle all of our widgets so things look pretty on screen choices = [_prep(i, w) for i, w in enumerate(text)] displayed = ColumnWidget([(78, choices)], 1) self._window.append(displayed) return True def input(self, args, key): """ Handle the input; this decides the repo source. """ try: num = int(key) except ValueError: return key if args == 3: # network install self._selection = num if self._selection == 1: # closest mirror self.set_source_closest_mirror() self.apply() self.close() return INPUT_PROCESSED elif self._selection in range(2, 5): # preliminary URL source switch self.set_source_url() newspoke = SpecifyRepoSpoke(self.app, self.data, self.storage, self.payload, self.instclass, self._selection) self.app.switch_screen_modal(newspoke) self.apply() self.close() return INPUT_PROCESSED elif self._selection == 5: # nfs # preliminary NFS source switch self.set_source_nfs() newspoke = SpecifyNFSRepoSpoke(self.app, self.data, self.storage, self.payload, self.instclass, self._selection, self._error) self.app.switch_screen_modal(newspoke) self.apply() self.close() return INPUT_PROCESSED elif num == 2: # local ISO file (HDD ISO) self._selection = num newspoke = SelectDeviceSpoke(self.app, self.data, self.storage, self.payload, self.instclass) self.app.switch_screen_modal(newspoke) self.apply() self.close() return INPUT_PROCESSED else: # mounted ISO if num == 1: # iso selected, just set some vars and return to main hub self.set_source_cdrom() self.payload.install_device = self._cdrom self.apply() self.close() return INPUT_PROCESSED else: self.app.switch_screen(self, num) return INPUT_PROCESSED @property def ready(self): """ Check if the spoke is ready. """ return (self._ready and not threadMgr.get(THREAD_PAYLOAD) and not threadMgr.get(THREAD_CHECK_SOFTWARE)) def apply(self): """ Execute the selections made. """ # If askmethod was provided on the command line, entering the source # spoke wipes that out. if flags.askmethod: flags.askmethod = False # if we had any errors, e.g. from a previous attempt to set the source, # clear them at this point self._error = False payloadMgr.restartThread(self.storage, self.data, self.payload, self.instclass, checkmount=False)
class SelectISOSpoke(NormalTUISpoke, SourceSwitchHandler): """ Select an ISO to use as install source. """ title = N_("Select an ISO to use as install source") category = SoftwareCategory def __init__(self, app, data, storage, payload, instclass, device): NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) SourceSwitchHandler.__init__(self) self.selection = None self.args = self.data.method self._device = device self._mount_device() self._isos = self._getISOs() def refresh(self, args=None): NormalTUISpoke.refresh(self, args) if self._isos: isos = [TextWidget(iso) for iso in self._isos] def _prep(i, w): """ Mangle our text to make it look pretty on screen. """ number = TextWidget("%2d)" % (i + 1)) return ColumnWidget([(4, [number]), (None, [w])], 1) # gnarl and mangle all of our widgets so things look pretty on screen choices = [_prep(i, w) for i, w in enumerate(isos)] displayed = ColumnWidget([(78, choices)], 1) self._window.append(displayed) else: message = _("No *.iso files found in device root folder") self._window += [TextWidget(message), ""] return True def input(self, args, key): if key == "c": self.apply() self.close() return key try: num = int(key) # get the ISO path self._current_iso_path = self._isos[num - 1] self.apply() self.close() return True except (IndexError, ValueError): return key @property def indirect(self): return True def _mount_device(self): """ Mount the device so we can search it for ISOs. """ mounts = get_mount_paths(self._device.path) # We have to check both ISO_DIR and the DRACUT_ISODIR because we # still reference both, even though /mnt/install is a symlink to # /run/install. Finding mount points doesn't handle the symlink if ISO_DIR not in mounts and DRACUT_ISODIR not in mounts: # We're not mounted to either location, so do the mount self._device.format.mount(mountpoint=ISO_DIR) def _unmount_device(self): self._device.format.unmount() def _getISOs(self): """List all *.iso files in the root folder of the currently selected device. TODO: advanced ISO file selection :returns: a list of *.iso file paths :rtype: list """ isos = [] for filename in os.listdir(ISO_DIR): if fnmatch.fnmatch(filename.lower(), "*.iso"): isos.append(filename) return isos def apply(self): """ Apply all of our changes. """ if self._current_iso_path: # If a hdd iso source has already been selected previously we need # to clear it now. # Otherwise we would get a crash if the same iso was selected again # as _unmount_device() would try to unmount a partition that is in use # due to the payload still holding on to the ISO file. if self.data.method.method == "harddrive": self.unset_source() self.set_source_hdd_iso(self._device, self._current_iso_path) # unmount the device - the payload will remount it anyway # (if it uses it) self._unmount_device()
class SelectDeviceSpoke(NormalTUISpoke): """ Select device containing the install source ISO file. """ title = N_("Select device containing the ISO file") category = SoftwareCategory def __init__(self, app, data, storage, payload, instclass): NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) self._currentISOFile = None self._mountable_devices = self._get_mountable_devices() self._device = None @property def indirect(self): return True def _sanitize_model(self, model): return model.replace("_", " ") def _get_mountable_devices(self): disks = [] fstring = "%(model)s %(path)s (%(size)s MB) %(format)s %(label)s" for dev in potentialHdisoSources(self.storage.devicetree): # path model size format type uuid of format dev_info = { "model": self._sanitize_model(dev.disk.model), "path": dev.path, "size": dev.size, "format": dev.format.name or "", "label": dev.format.label or dev.format.uuid or "" } disks.append([dev, fstring % dev_info]) return disks def refresh(self, args=None): NormalTUISpoke.refresh(self, args) # check if the storage refresh thread is running if threadMgr.get(THREAD_STORAGE_WATCHER): # storage refresh is running - just report it # so that the user can refresh until it is done # TODO: refresh once the thread is done ? message = _(PAYLOAD_STATUS_PROBING_STORAGE) self._window += [TextWidget(message), ""] return True # check if there are any mountable devices if self._mountable_devices: def _prep(i, w): """ Mangle our text to make it look pretty on screen. """ number = TextWidget("%2d)" % (i + 1)) return ColumnWidget([(4, [number]), (None, [w])], 1) devices = [TextWidget(d[1]) for d in self._mountable_devices] # gnarl and mangle all of our widgets so things look pretty on # screen choices = [_prep(i, w) for i, w in enumerate(devices)] displayed = ColumnWidget([(78, choices)], 1) self._window.append(displayed) else: message = _("No mountable devices found") self._window += [TextWidget(message), ""] return True def input(self, args, key): try: # try to switch to one of the mountable devices # to look for ISOs num = int(key) device = self._mountable_devices[num - 1][0] # get the device object self._device = device newspoke = SelectISOSpoke(self.app, self.data, self.storage, self.payload, self.instclass, device) self.app.switch_screen_modal(newspoke) self.close() return True except (IndexError, ValueError): # either the input was not a number or # we don't have the disk for the given number return key # Override Spoke.apply def apply(self): pass