[PATCH 1/2] text based storage spoke.
Jesse Keating
jkeating at redhat.com
Tue Aug 14 17:54:45 UTC 2012
A fair amount of this code comes from the gui storage spoke. When we
refactor things this will hopefully see a reduction in code.
Initially we can only pick disks to use and what way to clear the
partitions. Autopartitioning (plain) is forced.
---
pyanaconda/ui/tui/spokes/storage.py | 355 ++++++++++++++++++++++++++++++++++++
1 file changed, 355 insertions(+)
create mode 100644 pyanaconda/ui/tui/spokes/storage.py
diff --git a/pyanaconda/ui/tui/spokes/storage.py b/pyanaconda/ui/tui/spokes/storage.py
new file mode 100644
index 0000000..bfa90ac
--- /dev/null
+++ b/pyanaconda/ui/tui/spokes/storage.py
@@ -0,0 +1,355 @@
+# Text storage configuration spoke classes
+#
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details. You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): Jesse Keating <jkeating at redhat.com>
+#
+# Some of the code here is copied from pyanaconda/ui/gui/spokes/storage.py
+# which has the same license and authored by David Lehman <dlehman at redhat.com>
+#
+
+from pyanaconda.ui.tui.spokes import NormalTUISpoke
+from pyanaconda.ui.tui.simpleline import TextWidget, CheckboxWidget
+
+from pyanaconda.storage.size import Size
+from pyanaconda.flags import flags
+from pyanaconda.kickstart import doKickstartStorage
+
+from pykickstart.constants import *
+
+import logging
+log = logging.getLogger("anaconda")
+
+import gettext
+
+_ = lambda x: gettext.ldgettext("anaconda", x)
+P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
+
+__all__ = ["StorageSpoke", "AutoPartSpoke"]
+
+CLEARALL = _("Use All Space")
+CLEARLINUX = _("Replace Existing Linux system(s)")
+CLEARNONE = _("Use Free Space")
+
+parttypes = {CLEARALL: CLEARPART_TYPE_ALL, CLEARLINUX: CLEARPART_TYPE_LINUX,
+ CLEARNONE: CLEARPART_TYPE_NONE}
+
+class FakeDiskLabel(object):
+ def __init__(self, free=0):
+ self.free = free
+
+class FakeDisk(object):
+ def __init__(self, name, size=0, free=0, partitioned=True, vendor=None,
+ model=None, serial=None, removable=False):
+ self.name = name
+ self.size = size
+ self.format = FakeDiskLabel(free=free)
+ self.partitioned = partitioned
+ self.vendor = vendor
+ self.model = model
+ self.serial = serial
+ self.removable = removable
+
+ @property
+ def description(self):
+ return "%s %s" % (self.vendor, self.model)
+
+def getDisks(devicetree, fake=False):
+ if not fake:
+ disks = [d for d in devicetree.devices if d.isDisk and
+ not d.format.hidden and
+ not (d.protected and
+ d.removable)]
+ else:
+ disks = []
+ disks.append(FakeDisk("sda", size=300000, free=10000, serial="00001",
+ vendor="Seagate", model="Monster"))
+ disks.append(FakeDisk("sdb", size=300000, free=300000, serial="00002",
+ vendor="Seagate", model="Monster"))
+ disks.append(FakeDisk("sdc", size=8000, free=2100, removable=True,
+ vendor="SanDisk", model="Cruzer", serial="00003"))
+
+ return disks
+
+class StorageSpoke(NormalTUISpoke):
+ title = _("Install Destination")
+ category = "destination"
+
+ def __init__(self, app, data, storage, payload, instclass):
+ NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
+ self.selected_disks = self.data.ignoredisk.onlyuse[:]
+
+ # 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 = []
+
+ @property
+ def completed(self):
+ return bool(self.selected_disks and not self.errors)
+
+ @property
+ def status(self):
+ """ A short string describing the current status of storage setup. """
+ msg = _("No disks selected")
+ if self.data.ignoredisk.onlyuse:
+ msg = P_(("%d disk selected"),
+ ("%d disks selected"),
+ len(self.data.ignoredisk.onlyuse)) % len(self.data.ignoredisk.onlyuse)
+
+ if self.errors:
+ msg = _("Error checking storage configuration")
+ # Maybe show what type of clearpart and which disks selected?
+ elif self.data.autopart.autopart:
+ msg = _("Automatic partitioning selected")
+ else:
+ msg = _("Custom partitioning selected")
+
+ return msg
+
+ def _update_disk_list(self, disk):
+ """ Update self.selected_disks based on the selection."""
+
+ name = disk.name
+
+ # if the disk isn't already selected, select it.
+ if name not in self.selected_disks:
+ self.selected_disks.append(name)
+ # If the disk is already selected, deselect it.
+ elif name in self.selected_disks:
+ self.selected_disks.remove(name)
+
+ def _update_summary(self):
+ """ Update the summary based on the UI. """
+ count = 0
+ capacity = 0
+ free = Size(bytes=0)
+
+ # pass in our disk list so hidden disks' free space is available
+ free_space = self.storage.getFreeSpace(disks=self.disks)
+ selected = [d for d in self.disks if d.name in self.selected_disks]
+
+ for disk in selected:
+ capacity += disk.size
+ free += free_space[disk.name][0]
+ count += 1
+
+ summary = (P_(("%d disk selected; %s capacity; %s free ..."),
+ ("%d disks selected; %s capacity; %s free ..."),
+ count) % (count, str(Size(spec="%s MB" % capacity)), free))
+
+ if count == 0:
+ summary = (_("No disks selected; please select at least one disk to install to."))
+
+ return summary
+
+ def refresh(self, args = None):
+ NormalTUISpoke.refresh(self, args)
+
+ # Join the initialization thread to block on it
+ from pyanaconda.threads import threadMgr
+
+ initThread = threadMgr.get("AnaStorageWatcher")
+ if initThread:
+ # This print is foul. Need a better message display
+ print(_("Probing storage..."))
+ initThread.join()
+
+ # synchronize our local data store with the global ksdata
+ # Commment out because there is no way to select a disk right
+ # now without putting it in ksdata. Seems wrong?
+ #self.selected_disks = self.data.ignoredisk.onlyuse[:]
+ self.autopart = self.data.autopart.autopart
+
+ message = self._update_summary()
+
+ # loop through the disks and present them.
+ for disk in self.disks:
+ c = CheckboxWidget(title="%i) %s" % (self.disks.index(disk) + 1,
+ disk.name),
+ completed=(disk.name in self.selected_disks))
+ self._window += [c, ""]
+
+ self._window += [TextWidget(message), ""]
+
+ return True
+
+ def input(self, args, key):
+ """Grab the disk choice and update things"""
+
+ if key == "c":
+ if self.selected_disks:
+ 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):
+ return key
+
+ def apply(self):
+ if not flags.automatedInstall:
+ # default to using autopart for interactive installs
+ self.data.autopart.autopart = True
+
+ self.autopart = self.data.autopart.autopart
+ self.data.ignoredisk.onlyuse = self.selected_disks[:]
+ self.data.clearpart.drives = self.selected_disks[:]
+
+ # no thanks, lvm
+ self.data.autopart.type = AUTOPART_TYPE_PLAIN
+
+ if self.autopart:
+ self.clearPartType = CLEARPART_TYPE_ALL
+ else:
+ self.clearPartType = CLEARPART_TYPE_NONE
+
+ 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)
+
+ self.data.bootloader.location = "mbr"
+
+ self.storage.config.update(self.data)
+
+ # If autopart is selected we want to remove whatever has been
+ # created/scheduled to make room for autopart.
+ # If custom is selected, we want to leave alone any storage layout the
+ # user may have set up before now.
+ self.storage.config.clearNonExistent = self.data.autopart.autopart
+
+ # Pick the first disk to be the destination device for the bootloader.
+ # This appears to be the minimum amount of configuration required to
+ # make autopart happy with the bootloader settings.
+ if not self.data.bootloader.bootDrive:
+ self.data.bootloader.bootDrive = self.storage.bootloader.disks[0].name
+
+ def execute(self):
+ print(_("Generating updated storage configuration"))
+ try:
+ # Try to do the storage, absorb any tracebacks for now
+ # We will catch any failures when checking the sanity
+ doKickstartStorage(self.storage, self.data, self.instclass)
+ except:
+ pass
+ # Doing this in foreground now, candidate for background thread
+ print(_("Checking storage configuration..."))
+ (self.errors, warnings) = self.storage.sanityCheck()
+ for e in self.errors:
+ log.error(e)
+ for w in warnings:
+ log.warn(w)
+
+ def initialize(self):
+ from pyanaconda.threads import threadMgr, AnacondaThread
+
+ NormalTUISpoke.initialize(self)
+
+ threadMgr.add(AnacondaThread(name="AnaStorageWatcher",
+ target=self._initialize))
+
+ self.selected_disks = self.data.ignoredisk.onlyuse[:]
+ # Probably need something here to track which disks are selected?
+
+ def _initialize(self):
+ # Secondary initialize so wait for the storage thread
+ # to complete before populating our disk list
+
+ from pyanaconda.threads import threadMgr
+
+ storageThread = threadMgr.get("AnaStorageThread")
+ if storageThread:
+ storageThread.join()
+
+ self.disks = sorted(getDisks(self.storage.devicetree),
+ key=lambda d: d.name)
+
+ self._update_summary()
+ self._ready = True
+
+class AutoPartSpoke(NormalTUISpoke):
+ title = _("Autopartitioning Options")
+ category = "destination"
+
+ def __init__(self, app, data, storage, payload, instclass):
+ NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
+ self.clearPartType = self.data.clearpart.type
+
+ @property
+ def indirect(self):
+ return True
+
+ @property
+ def completed(self):
+ return True # We're always complete
+
+ def refresh(self, args = None):
+ NormalTUISpoke.refresh(self, args)
+ # synchronize our local data store with the global ksdata
+ self.clearPartType = self.data.clearpart.type
+ # I dislike "is None", but bool(0) returns false :(
+ if self.clearPartType is None:
+ # Default to clearing everything.
+ self.clearPartType = CLEARPART_TYPE_ALL
+
+ self.parttypelist = sorted(parttypes.keys())
+ for parttype in self.parttypelist:
+ c = CheckboxWidget(title="%i) %s" % (self.parttypelist.index(parttype) + 1,
+ parttype),
+ completed=(parttypes[parttype] == self.clearPartType))
+ self._window += [c, ""]
+
+ message = _("Installation requires partitioning of your hard drive. Select what space to use for the install target.")
+
+ self._window += [TextWidget(message), ""]
+
+ return True
+
+ def apply(self):
+ self.data.clearpart.type = self.clearPartType
+ self.data.clearpart.initAll = True
+
+ def input(self, args, key):
+ """Grab the choice and update things"""
+
+ if key == "c":
+ self.apply()
+ self.close()
+ return False
+
+ try:
+ number = int(key)
+ self.clearPartType = parttypes[self.parttypelist[number -1]]
+ self.apply()
+ self.close()
+ return False
+
+ except (ValueError, KeyError):
+ return key
--
1.7.11.2
More information about the anaconda-patches
mailing list