[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