From a2aa5722a57953d4b58f047ead029de68b4df2b7 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Thu, 26 Jul 2012 21:13:32 -0500
Subject: [PATCH 6/8] Add an IR for the devices in the custom UI.

---
 pyanaconda/ui/gui/spokes/custom.py        |  463 ++++++++++++++++++++++++-----
 pyanaconda/ui/gui/spokes/lib/accordion.py |    5 +-
 2 files changed, 386 insertions(+), 82 deletions(-)

diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index 2793957..c2c0c5a 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -36,6 +36,7 @@ from pykickstart.constants import *
 
 from pyanaconda.product import productName, productVersion
 from pyanaconda.storage.formats import device_formats
+from pyanaconda.storage.formats import getFormat
 from pyanaconda.storage.size import Size
 from pyanaconda.storage import Root
 from pyanaconda.storage.partitioning import doPartitioning
@@ -51,6 +52,9 @@ from pyanaconda.ui.gui.categories.storage import StorageCategory
 
 from gi.repository import Gtk
 
+import logging
+log = logging.getLogger("anaconda")
+
 __all__ = ["CustomPartitioningSpoke"]
 
 new_install_name = _("New %s %s Installation") % (productName, productVersion)
@@ -125,8 +129,48 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         self._current_selector = None
         self._when_create_text = ""
+        self._ui_devices = []
+        self._deleted_devices = []
+        self._free_space = Size(bytes=0)
 
     def apply(self):
+        # schedule actions for device removals, resizes
+        for removed in self._deleted_devices:
+            if removed.device_id is None:
+                # created here, destroyed here -> NOOP
+                continue
+
+            log.debug("ui: removing device %s" % removed.name)
+            device = self.storage.devicetree.getDeviceByName(removed.name)
+            self.storage.destroyDevice(device)
+
+        for ui_device in self._ui_devices:
+            if ui_device.device_id is None:
+                # we're creating a device
+
+                # this isn't a leaf device
+                if ui_device.create_type is None:
+                    continue
+
+                self.storage.newUIDevice(ui_device.create_type,
+                                         ui_device.size,
+                                         mountpoint=ui_device.mountpoint,
+                                         fstype=ui_device.fstype,
+                                         encrypted=ui_device.encrypted,
+                                         level=ui_device.raid_level,
+                                         disks=self.storage.disks)
+
+            # this is a device that's already in the tree
+            device = self.storage.devicetree.getDeviceByName(ui_device.name)
+            if ui_device.resizable and ui_device.size != device.size:
+                # schedule a resize action for the device and its format
+                pass
+
+        # XXX How can we handle edits?
+        #
+        #       Doesn't matter for now since we don't have support for editing
+        #       devices.
+
         self.storage.setUpBootLoader()
         StorageChecker.run(self)
 
@@ -147,7 +191,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         self._configButton = self.builder.get_object("configureButton")
 
     def initialize(self):
-        from pyanaconda.storage.devices import DiskDevice
         from pyanaconda.storage.formats.fs import FS
 
         NormalSpoke.initialize(self)
@@ -205,16 +248,17 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         from pyanaconda.storage.devices import DiskDevice
         return [d for d in self.storage.unusedDevices if d.disks and not isinstance(d, DiskDevice)]
 
-    def _currentFreeSpace(self):
+    def _setCurrentFreeSpace(self):
         """Add up all the free space on selected disks and return it as a Size."""
         totalFree = Size(bytes=0)
 
         freeDisks = self.storage.getFreeSpace(disks=self._clearpartDevices())
-        for tup in freeDisks.values():
-            for chunk in tup:
-                totalFree += chunk
+        for (disk_free, fs_free) in freeDisks.values():
+            # only show actual free space as free -- don't include space that
+            # could be gained by shrinking filesystems
+            totalFree += disk_free
 
-        return totalFree
+        self._free_space = totalFree
 
     def _currentTotalSpace(self):
         """Add up the sizes of all selected disks and return it as a Size."""
@@ -231,7 +275,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         self._totalSpaceLabel = self.builder.get_object("totalSpaceLabel")
         self._summaryButton = self.builder.get_object("summary_button")
 
-        self._availableSpaceLabel.set_text(str(self._currentFreeSpace()))
+        self._availableSpaceLabel.set_text(str(self._free_space))
         self._totalSpaceLabel.set_text(str(self._currentTotalSpace()))
 
         summaryLabel = self._summaryButton.get_children()[0]
@@ -245,7 +289,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
     def refresh(self):
         NormalSpoke.refresh(self)
+        self._deleted_devices = []
+        self._ui_devices = get_ui_devices(self.storage)
         self._do_refresh()
+
+        # update our free space number based on Storage
+        self._setCurrentFreeSpace()
+
         self._updateSpaceDisplay()
 
     def _do_refresh(self):
@@ -258,12 +308,45 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         # Now it's time to populate the accordion.
 
+        # set up a dict with name keys for the ui device list
+        ui_device_dict = {}
+        for device in self._ui_devices:
+            ui_device_dict[device.name] = device
+
         # We can only have one page expanded at a time.
         did_expand = False
 
-        unused = self._unusedDevices()
-        new_devices = [d for d in self.storage.devicetree.leaves if d not in unused and not d.exists]
-        roots = self.storage.roots[:]
+        unused = [ui_device_dict[d.name] for d in self._unusedDevices()
+                    if d.name in ui_device_dict and
+                       ui_device_dict[d.name].device_id is not None]
+        new_devices = [d for d in self._ui_devices if not d.exists]
+
+        log.debug("ui: unused=%s" % [d.name for d in unused])
+        log.debug("ui: new_devices=%s" % [d.name for d in new_devices])
+
+        ui_roots = []
+        for root in self.storage.roots:
+            name = root.name
+            swaps = []
+            mounts = {}
+            for (mountpoint, device) in root.mounts.items():
+                ui_device = ui_device_dict.get(device.name)
+                if not ui_device or not ui_device.exists:
+                    continue
+
+                mounts[mountpoint] = ui_device
+
+            for device in root.swaps:
+                ui_device = ui_device_dict.get(device.name)
+                if not ui_device or not ui_device.exists:
+                    continue
+
+                swaps.append(ui_device)
+
+            if not swaps and not mounts:
+                continue
+
+            ui_roots.append(UIRoot(name=name, mounts=mounts, swaps=swaps))
 
         # If we've not yet run autopart, add an instance of CreateNewPage.  This
         # ensures it's only added once.
@@ -278,39 +361,30 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             label = self.builder.get_object("whenCreateLabel")
             label.set_text(self._when_create_text % (productName, productVersion))
         else:
-            swaps = [d for d in new_devices if d in self.storage.swaps]
-            mounts = dict([(d.format.mountpoint, d) for d in new_devices if getattr(d.format, "mountpoint", None)])
-            new_root = Root(mounts=mounts, swaps=swaps, name=new_install_name)
-            roots.insert(0, new_root)
+            swaps = [d for d in new_devices if d.fstype == "swap"]
+            mounts = dict([(d.mountpoint, d) for d in new_devices if d.mountpoint])
+            new_root = UIRoot(mounts=mounts, swaps=swaps, name=new_install_name)
+            ui_roots.insert(0, new_root)
 
         # Add in all the existing (or autopart-created) operating systems.
-        for root in roots:
-            if root.device and root.device not in self.storage.devices:
-                continue
-
+        for root in ui_roots:
             page = Page()
             page.pageTitle = root.name
 
             for device in root.swaps:
-                if device not in self.storage.devices:
-                    continue
-
                 selector = page.addDevice("Swap", device.size, None, self.on_selector_clicked)
                 selector._device = device
                 selector._root = root
 
             for (mountpoint, device) in root.mounts.iteritems():
-                if device not in self.storage.devices:
-                    continue
-
-                selector = page.addDevice(self._mountpointName(mountpoint) or device.format.name, device.size, mountpoint, self.on_selector_clicked)
+                selector = page.addDevice(self._mountpointName(mountpoint) or device.fstype, device.size, mountpoint, self.on_selector_clicked)
                 selector._device = device
                 selector._root = root
 
             page.show_all()
             self._accordion.addPage(page, cb=self.on_page_clicked)
 
-            if not did_expand and getattr(self._current_selector, "_root", None) and root.name == self._current_selector._root.name:
+            if not did_expand:
                 did_expand = True
                 self._accordion.expandPage(root.name)
 
@@ -320,14 +394,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             page.pageTitle = _("Unknown")
 
             for u in unused:
-                selector = page.addDevice(u.format.name, u.size, None, self.on_selector_clicked)
+                selector = page.addDevice(u.fstype, u.size, None, self.on_selector_clicked)
                 selector._device = u
                 selector._root = None
 
             page.show_all()
             self._accordion.addPage(page, cb=self.on_page_clicked)
 
-            if not did_expand and self._current_selector and unused == self._current_selector._root:
+            if not did_expand:
                 did_expand = True
                 self._accordion.expandPage(page.pageTitle)
 
@@ -361,8 +435,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         device = selector._device
 
-        if hasattr(device.format, "label") and labelEntry.get_text():
-            device.format.label = labelEntry.get_text()
+        if labelEntry.get_text():
+            device.label = labelEntry.get_text()
 
     def _populate_right_side(self, selector):
         encryptCheckbox = self.builder.get_object("encryptCheckbox")
@@ -378,16 +452,18 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         selectedDeviceLabel.set_text(selector.props.name)
         selectedDeviceDescLabel.set_text(self._description(selector.props.name))
 
-        labelEntry.set_text(getattr(device.format, "label", "") or "")
-        labelEntry.set_sensitive(getattr(device.format, "labelfsProg", "") != "")
+        labelEntry.set_text(device.label or "")
+        can_label = getattr(getFormat(device.fstype), "labelfsProg", "") != ""
+        labelEntry.set_sensitive(can_label)
 
         if labelEntry.get_sensitive():
             labelEntry.props.has_tooltip = False
         else:
             labelEntry.set_tooltip_text(_("This file system does not support labels."))
 
-        sizeSpinner.set_range(device.minSize, device.maxSize)
-        sizeSpinner.set_value(device.size)
+        sizeSpinner.set_range(device.min_size.convertTo(spec="mb"),
+                              device.max_size.convertTo(spec="mb"))
+        sizeSpinner.set_value(device.size.convertTo(spec="mb"))
         sizeSpinner.set_sensitive(device.resizable)
 
         if sizeSpinner.get_sensitive():
@@ -398,17 +474,19 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         encryptCheckbox.set_active(device.encrypted)
 
         # FIXME:  What do we do if we can't figure it out?
-        if device.type == "lvmlv":
+        if device.type == "LVM Logical Volume":
             typeCombo.set_active(1)
-        elif device.type in ["dm-raid array", "mdarray"]:
+        elif device.type == "Software RAID":
             typeCombo.set_active(2)
-        elif device.type == "partition":
+        elif device.type == "Partition":
             typeCombo.set_active(3)
+        elif device.type.startswith("BTRFS"):
+            typeCombo.set_active(0)
 
         # FIXME:  What do we do if we can't figure it out?
         model = fsCombo.get_model()
         for i in range(0, len(model)):
-            if model[i][0] == device.format.name:
+            if model[i][0] == device.fstype:
                 fsCombo.set_active(i)
                 break
 
@@ -439,8 +517,12 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # create a device of the default type, using any disks, with an
         # appropriate fstype and mountpoint
         mountpoint = dialog.mountpoint
-        size = dialog.size
+        size = min(dialog.size, self._free_space)
         fstype = self.storage.defaultFSType
+
+        # we're doing nothing here to ensure that bootable requests end up on
+        # the boot disk, but the weight from platform should take care of this
+
         if mountpoint.lower() == "swap":
             fstype = "swap"
             mountpoint = None
@@ -455,32 +537,36 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
              mountpoint in self.storage.mountpoints.keys():
             return
 
-        if not size:
-            # no size specified, so use the default size
-            size = None
-
-        if self.data.autopart.type == AUTOPART_TYPE_PLAIN:
-            # create a partition for this new filesystem
-            size_mb = float(size.convertTo(spec="mb"))
-            device = self.storage.newPartition(size=size_mb,
-                                               fmt_type=fstype,
-                                               mountpoint=mountpoint)
-            self.storage.createDevice(device)
-            try:
-                doPartitioning(self.storage)
-            except StorageError as e:
-                actions = self.storage.devicetree.findActions(device=device)
-                for a in reversed(actions):
-                    self.storage.devicetree.cancelAction(a)
-            else:
-                self._do_refresh()
+        if self.data.autopart.type == AUTOPART_TYPE_LVM and \
+             mountpoint != "/boot/efi":
+            device_class = UILogicalVolume
+        elif self.data.autopart.type == AUTOPART_TYPE_BTRFS and \
+             fstype != "swap" and \
+             not mountpoint.startswith("/boot"):
+            device_class = UIBTRFSSubVolume
+        else:
+            device_class = UIPartition
+
+        device = device_class(mountpoint=mountpoint, size=size, fstype=fstype)
+        self._ui_devices.append(device)
+        self._free_space -= device.disk_space
+        self._do_refresh()
+        self._updateSpaceDisplay()
 
     def _destroy_device(self, device):
+        self._ui_devices.remove(device)
+        self._deleted_devices.append(device)
+
+        # XXX This could be tricky. We don't want to count an LV's size twice
+        #     by counting it for the LV and again for the VG.
+        if not device.exists or device.type == "Partition":
+            log.debug("ui: destroy_device: space=%s  size=%s  free=%s" % (device.disk_space, device.size, self._free_space))
+            self._free_space += device.disk_space
+
         # if this device has parents with no other children, remove them too
-        parents = device.parents[:]
-        self.storage.destroyDevice(device)
-        for parent in parents:
-            if parent.kids == 0 and not parent.isDisk:
+        for parent in device.parents:
+            all_parents = [p for d in self._ui_devices for p in d.parents]
+            if parent not in all_parents and parent.type != "Disk":
                 self._destroy_device(parent)
 
     def _remove_from_root(self, root, device):
@@ -488,8 +574,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             pass    # unused device
         elif device in root.swaps:
             root.swaps.remove(device)
-        elif hasattr(device.format, "mountpoint") and \
-             device in root.mounts.values():
+        elif device.mountpoint and device in root.mounts.values():
             mountpoints = [m for (m,d) in root.mounts.items() if d == device]
             for mountpoint in mountpoints:
                 root.mounts.pop(mountpoint)
@@ -520,7 +605,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
                 # schedule actions to delete the thing.
                 dialog = ConfirmDeleteDialog(self.data)
                 with enlightbox(self.window, dialog.window):
-                    dialog.refresh(getattr(device.format, "mountpoint", None), device.name)
+                    dialog.refresh(device.mountpoint, device.name)
                     rc = dialog.run()
 
                     if rc == 0:
@@ -530,12 +615,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
             self._remove_from_root(root, device)
             self._destroy_device(device)
-
-            # If the root is now empty, remove it. Devices from the Unused page
-            # will have no root.
-            if root and root in self.storage.roots and len(root.swaps + root.mounts.values()) == 0:
-                self.storage.roots.remove(root)
-
             self._update_ui_for_removals()
         elif self._accordion.currentPage():
             # This is a complete installed system.  Thus, we first need to confirm
@@ -562,11 +641,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             for device in root.swaps + root.mounts.values():
                 self._destroy_device(device)
 
-            # Remove the root entirely.  This will cause the empty Page to be
-            # deleted from the left hand side.
-            if root in self.storage.roots:
-                self.storage.roots.remove(root)
-
             self._update_ui_for_removals()
 
     def on_summary_clicked(self, button):
@@ -616,9 +690,50 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # Then do autopartitioning.  We do not do any clearpart first.  This is
         # custom partitioning, so you have to make your own room.
         # FIXME:  Handle all the autopart exns here.
-        self.data.autopart.autopart = True
-        self.data.autopart.execute(self.storage, self.data, self.instclass)
-        self.data.autopart.autopart = False
+
+        # UIDeviceFIXME: relative sizes of requests
+        # Approach:
+        #
+        #   NOTE: There shouldn't be any non-existent partitions in play at this
+        #         point.
+        #
+        #   XXX this estimate is going to suck for everything except lvm since
+        #       the accuracy is reduced for each additional partition request
+        #
+        #   - use DiskChunk and VGChunk to get a decent estimate
+        #       - use DiskChunk to establish partition/container size
+        #       - use VGChunk to establish lv size based on container size
+        #         obtained above
+        for request in self.storage.autoPartitionRequests:
+            if request.requiredSpace and \
+               request.requiredSpace > self._free_space:
+                continue
+
+            # figure out the device type for this request
+            if request.lv and self.data.autopart.type == AUTOPART_TYPE_LVM:
+                device_class = UILogicalVolume
+            elif request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS:
+                device_class = UIBTRFSSubVolume
+            else:
+                device_class = UIPartition
+
+            if request.fstype is None:
+                request.fstype = self.storage.getFSType(mountpoint=request.mountpoint)
+            elif request.fstype in ("prepboot", "efi", "biosboot", "hfs+") and \
+                 request.fstype in [d.fstype for d in self._ui_devices]:
+                #
+                # XXX We could use the code directly from storage.partitioning
+                #     since this function can only be called when no new devices
+                #     have been requested.
+                #
+                # there should never be a need for more than one of these
+                # partitions, so skip them.
+                continue
+
+            new_device = device_class(size=Size(spec="%fmb" % request.size),
+                                      fstype=request.fstype,
+                                      mountpoint=mountpoint)
+            self._ui_devices.append(new_device)
 
         # Refresh the spoke to make the new partitions appear.
         self.refresh()
@@ -641,3 +756,193 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             self._optionsNotebook.set_current_page(2)
         elif text == _("Standard Partition"):
             self._optionsNotebook.hide()
+
+class UIDevice(object):
+    type = "Generic Device"
+    raid_levels = []
+    create_type = None
+
+    def __init__(self, size=None, mountpoint=None, fstype=None, name=None,
+                 raid_level=None, parents=None, exists=False, encrypted=False,
+                 label=None, resizable=False, min_size=None, max_size=None,
+                 device_id=None):
+        self.size = size
+        self.mountpoint = mountpoint
+        self.fstype = fstype
+        self.name = name
+        self.raid_level = raid_level
+        self.parents = parents or []
+        self.exists = exists
+        self.encrypted = encrypted
+        self.label = label
+
+        self.resizable = resizable
+        self.min_size = min_size
+        self.max_size = max_size
+
+        self.device_id = device_id
+
+        if self.min_size is None:
+            self.min_size = self.size
+
+        if self.max_size is None:
+            self.max_size = self.size
+
+    @property
+    def disk_space(self):
+        space = self.size
+        if self.raid_level in ("raid1", "raid10"):
+            space = self.size * len(self.parents)
+        elif self.raid_level == "raid5":
+            space = self.size * (float(len(parents)) / (len(parents) - 1))
+        elif self.raid_level == "raid6":
+            space = self.size * (float(len(parents)) / (len(parents) - 2))
+
+        return space
+
+class UIDisk(UIDevice):
+    type = "Disk"
+
+class UIPartition(UIDevice):
+    type = "Partition"
+    create_type = AUTOPART_TYPE_PLAIN
+
+class UIVolumeGroup(UIDevice):
+    type = "LVM Volume Group"
+
+class UILogicalVolume(UIDevice):
+    type = "LVM Logical Volume"
+    raid_levels = ["linear", "raid0", "raid1", "raid10"]
+    create_type = AUTOPART_TYPE_LVM
+
+class UIRAIDArray(UIDevice):
+    type = "Software RAID"
+    raid_levels = ["raid0", "raid1", "raid5", "raid10"]
+
+class UIBTRFSVolume(UIDevice):
+    type = "BTRFS"
+    raid_levels = ["linear", "raid0", "raid1", "raid10"]
+    create_type = AUTOPART_TYPE_BTRFS
+
+class UIBTRFSSubVolume(UIDevice):
+    type = "BTRFS Sub-Volume"
+    create_type = AUTOPART_TYPE_BTRFS
+
+"""
+    UI for device editing
+
+        - convert current StorageDevice tree to UIDevice tree
+        - modify UIDevice tree in UI
+        - generate a list of actions to represent the changes
+"""
+def get_ui_device_class(device):
+    ui_class = None
+    if device.isDisk:
+        ui_class = UIDisk
+    elif device.type == "partition":
+        ui_class = UIPartition
+    elif device.type == "mdarray":
+        ui_class = UIRAIDArray
+    elif device.type == "lvmvg":
+        ui_class = UIVolumeGroup
+    elif device.type == "lvmlv":
+        ui_class = UILogicalVolume
+    elif device.type == "luks/dm-crypt":
+        ui_class = get_ui_device_type(device.parents[0])
+
+    log.debug("ui: get_ui_device_class returning %s for device %s"
+              % (ui_class, device))
+    return ui_class
+
+def get_ui_device_raid_level(device):
+    level = None
+    if device.type == "mdarray":
+        if device.level == mdraid.RAID0:
+            level = "raid0"
+        elif device.level == mdraid.RAID1:
+            level = "raid1"
+        elif device.level == mdraid.RAID5:
+            level = "raid5"
+        elif device.level == mdraid.RAID6:
+            level = "raid6"
+        elif device.level == mdraid.RAID10:
+            level = "raid10"
+    elif device.type == "lvmlv":
+        # TODO: implement striping and mirroring in LVMLogicalVolumeDevice
+        pass
+    elif device.type == "btrfs volume":
+        level = device.dataLevel
+
+    return level
+
+def get_ui_parents(storage_device, ui_devices):
+    ui_parents = []
+    for parent in storage_device.parents:
+        for u in ui_devices:
+            if u.name == parent.name:
+                ui_parents.append(u)
+
+    return ui_parents
+
+def get_ui_devices(storage):
+    devices = storage.devicetree.devices[:]
+    ui_devices = []
+    while devices:
+        roots = [d for d in devices if
+                        not [p for p in d.parents if p in devices]]
+        for device in roots:
+            ui_class = get_ui_device_class(device)
+            devices.remove(device)
+            if ui_class is None:
+                continue
+
+            min_size = device.size
+            max_size = device.size
+            resizable = False
+            if device.format.type == "luks" and \
+               storage.devicetree.getChildren(device):
+                continue
+            elif device.type == "luks/dm-crypt":
+                encrypted = True
+                raid_level = get_ui_device_raid_level(device.parents[0])
+                exists = device.parents[0].exists
+                devices.remove(device.parents[0])
+                parents = get_ui_parents(device.parents[0], ui_devices)
+            else:
+                encrypted = False
+                raid_level = get_ui_device_raid_level(device)
+                exists = device.exists
+                parents = get_ui_parents(device, ui_devices)
+                if device.resizable:
+                    resizable = True
+                    min_size = device.minSize
+                    max_size = device.maxSize
+
+            ui_device = ui_class(name=device.name,
+                                 size=Size(spec="%f mb" % device.size),
+                                 fstype=device.format.type,
+                                 mountpoint=getattr(device.format,
+                                                    "mountpoint", None),
+                                 raid_level=raid_level,
+                                 encrypted=encrypted,
+                                 label=getattr(device.format, "label", None),
+                                 parents=parents,
+                                 exists=exists,
+                                 resizable=resizable,
+                                 min_size=Size(spec="%f mb" % min_size),
+                                 max_size=Size(spec="%f mb" % max_size),
+                                 device_id=device.id)
+            ui_devices.append(ui_device)
+
+    return ui_devices
+
+class UIRoot(object):
+    def __init__(self, name=None, mounts=None, swaps=None):
+        self.name = name
+        self.mounts = mounts or {}
+        self.swaps = swaps or []
+
+    @property
+    def devices(self):
+        return self.mounts.values() + self.swaps[:]
+
diff --git a/pyanaconda/ui/gui/spokes/lib/accordion.py b/pyanaconda/ui/gui/spokes/lib/accordion.py
index c2da71e..09b87e9 100644
--- a/pyanaconda/ui/gui/spokes/lib/accordion.py
+++ b/pyanaconda/ui/gui/spokes/lib/accordion.py
@@ -23,7 +23,6 @@ import gettext
 _ = lambda x: gettext.ldgettext("anaconda", x)
 
 from pyanaconda.product import productName, productVersion
-from pyanaconda.storage.size import Size
 
 from gi.repository.AnacondaWidgets import MountpointSelector
 from gi.repository import Gtk
@@ -137,7 +136,7 @@ class Page(Gtk.Box):
 
     def addDevice(self, name, size, mountpoint, cb):
         selector = MountpointSelector()
-        selector = MountpointSelector(name, str(Size(spec="%s MB" % size)), mountpoint or "")
+        selector = MountpointSelector(name, str(size).upper(), mountpoint or "")
         selector.connect("button-press-event", self._onSelectorClicked, cb)
         selector.connect("key-release-event", self._onSelectorClicked, cb)
         selector.connect("focus-in-event", self._onSelectorClicked, cb)
@@ -182,7 +181,7 @@ class UnknownPage(Page):
 
     def addDevice(self, name, size, mountpoint, cb):
         selector = MountpointSelector()
-        selector = MountpointSelector(name, str(Size(spec="%s MB" % size)), mountpoint or "")
+        selector = MountpointSelector(name, str(size).upper(), mountpoint or "")
         selector.connect("button-press-event", self._onSelectorClicked, cb)
         selector.connect("key-release-event", self._onSelectorClicked, cb)
         selector.connect("focus-in-event", self._onSelectorClicked, cb)
-- 
1.7.7.6

