#
# Storage filtering UI
#
# Copyright (C) 2009  Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
#

import block
import collections
import gtk, gobject
import gtk.glade
import gui
import parted
import _ped
from DeviceSelector import *
from baseudev import *
from constants import *
from iw_gui import *
from storage.devices import devicePathToName
from storage.udev import *
from storage.devicelibs.mpath import *
from flags import flags
import storage.iscsi
import storage.fcoe
import storage.zfcp

import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)

DEVICE_COL = 4
MODEL_COL = 5
CAPACITY_COL = 6
VENDOR_COL = 7
INTERCONNECT_COL = 8
SERIAL_COL = 9
ID_COL = 10
PATHS_COL = 11
PORT_COL = 12
TARGET_COL = 13
LUN_COL = 14

# This is kind of a magic class that is used for populating the device store.
# It mostly acts like a list except for some funny behavior on adding/getting.
# You must add udev dicts to this list, but when you go to examine the list
# (by pulling items out, checking membership, etc.) you are comparing based
# on names.
#
# The only reason to have this is to prevent needing two lists in a variety
# of places throughout FilterWindow.
class NameCache(collections.MutableSequence):
    def __init__(self, iterable):
        self._lst = list(iterable)

    def __contains__(self, item):
        return item["name"] in iter(self)

    def __delitem__(self, index):
        return self._lst.__delitem__(index)

    def __getitem__(self, index):
        return self._lst.__getitem__(index)["name"]

    def __iter__(self):
        for d in self._lst:
            yield d["name"]

    def __len__(self):
        return len(self._lst)

    def __setitem__(self, index, value):
        return self._lst.__setitem__(index, value)

    def insert(self, index, value):
        return self._lst.insert(index, value)

# These are global because they need to be accessible across all Callback
# objects as the same values, and from the AdvancedFilterWindow object to add
# and remove devices when populating scrolled windows.
totalDevices = 0
selectedDevices = 0
totalSize = 0
selectedSize = 0

# These are global so they can be accessed from all Callback objects.  The
# basic callback defines its membership as anything that doesn't pass the
# is* methods.
def isCCISS(info):
    return udev_device_is_cciss(info)

def isRAID(info):
    if flags.dmraid:
        return udev_device_is_biosraid(info)

    return False

def isMultipath(info):
    return udev_device_is_multipath_member(info)

def isOther(info):
    return udev_device_is_iscsi(info) or udev_device_is_fcoe(info)

class Callbacks(object):
    def __init__(self, xml):
        self.model = None
        self.xml = xml

        self.sizeLabel = self.xml.get_widget("sizeLabel")
        self.sizeLabel.connect("realize", self.update)

    def addToUI(self, tuple):
        pass

    def deviceToggled(self, set, device):
        global selectedDevices, totalDevices
        global selectedSize, totalSize

        if set:
            selectedDevices += 1
            selectedSize += device["XXX_SIZE"]
        else:
            selectedDevices -= 1
            selectedSize -= device["XXX_SIZE"]

        self.update()

    def isMember(self, info):
        return info and not isRAID(info) and not isCCISS(info) and \
               not isMultipath(info) and not isOther(info)

    def update(self, *args, **kwargs):
        global selectedDevices, totalDevices
        global selectedSize, totalSize

        self.sizeLabel.set_markup(_("<b>%s device(s) (%s MB) selected</b> out of %s device(s) (%s MB) total.") % (selectedDevices, selectedSize, totalDevices, totalSize))

    def visible(self, model, iter, view):
        # Most basic visibility function - does the model say this row
        # should be visible?  Subclasses can define their own more specific
        # visibility function, though they should also take a look at this
        # one to see what the model says.
        return self.isMember(model.get_value(iter, OBJECT_COL)) and \
               model.get_value(iter, VISIBLE_COL)

class RAIDCallbacks(Callbacks):
    def isMember(self, info):
        return info and (isRAID(info) or isCCISS(info))

class FilteredCallbacks(Callbacks):
    def __init__(self, *args, **kwargs):
        Callbacks.__init__(self, *args, **kwargs)

        # Are we even applying the filtering UI?  This is False when
        # whateverFilterBy is empty, True the rest of the time.
        self.filtering = False

    def reset(self):
        self.notebook.set_current_page(0)
        self.filtering = False

    def set(self, num):
        self.notebook.set_current_page(num)
        self.filtering = True

class MPathCallbacks(FilteredCallbacks):
    def __init__(self, *args, **kwargs):
        FilteredCallbacks.__init__(self, *args, **kwargs)

        self._vendors = []
        self._interconnects = []

        self.filterBy = self.xml.get_widget("mpathFilterBy")
        self.notebook = self.xml.get_widget("mpathNotebook")

        self.vendorEntry = self.xml.get_widget("mpathVendorEntry")
        self.interconnectEntry = self.xml.get_widget("mpathInterconnectEntry")
        self.IDEntry = self.xml.get_widget("mpathIDEntry")

        self.mpathFilterHBox = self.xml.get_widget("mpathFilterHBox")
        self.mpathFilterHBox.connect("realize", self._populateUI)

        self.vendorEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.interconnectEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())

    def addToUI(self, tuple):
        if not tuple[VENDOR_COL] in self._vendors:
            self._vendors.append(tuple[VENDOR_COL])

        if not tuple[INTERCONNECT_COL] in self._interconnects:
            self._interconnects.append(tuple[INTERCONNECT_COL])

    def isMember(self, info):
        return info and isMultipath(info)

    def visible(self, model, iter, view):
        if not FilteredCallbacks.visible(self, model, iter, view):
            return False

        if self.filtering:
            if self.notebook.get_current_page() == 0:
                return self._visible_by_interconnect(model, iter, view)
            elif self.notebook.get_current_page() == 1:
                return self._visible_by_vendor(model, iter, view)
            elif self.notebook.get_current_page() == 2:
                return self._visible_by_wwid(model, iter, view)

        return True

    def _populateUI(self, widget):
        cell = gtk.CellRendererText()

        self._vendors.sort()
        self.vendorEntry.set_model(gtk.ListStore(gobject.TYPE_STRING))
        self.vendorEntry.pack_start(cell)
        self.vendorEntry.add_attribute(cell, 'text', 0)

        for v in self._vendors:
            self.vendorEntry.append_text(v)

        self.vendorEntry.show_all()

        self._interconnects.sort()
        self.interconnectEntry.set_model(gtk.ListStore(gobject.TYPE_STRING))
        self.interconnectEntry.pack_start(cell)
        self.interconnectEntry.add_attribute(cell, 'text', 0)

        for i in self._interconnects:
            self.interconnectEntry.append_text(i)

        self.interconnectEntry.show_all()

    def _visible_by_vendor(self, model, iter, view):
        entered = self.vendorEntry.get_child().get_text()
        return model.get_value(iter, VENDOR_COL).find(entered) != -1

    def _visible_by_interconnect(self, model, iter, view):
        entered = self.interconnectEntry.get_child().get_text()
        return model.get_value(iter, INTERCONNECT_COL).find(entered) != -1

    def _visible_by_wwid(self, model, iter, view):
        # FIXME:  make this support globs, etc.
        entered = self.IDEntry.get_text()

        return entered != "" and model.get_value(iter, ID_COL).find(entered) != -1

class OtherCallbacks(MPathCallbacks):
    def __init__(self, *args, **kwargs):
        FilteredCallbacks.__init__(self, *args, **kwargs)

        self._vendors = []
        self._interconnects = []

        self.filterBy = self.xml.get_widget("otherFilterBy")
        self.notebook = self.xml.get_widget("otherNotebook")

        self.vendorEntry = self.xml.get_widget("otherVendorEntry")
        self.interconnectEntry = self.xml.get_widget("otherInterconnectEntry")
        self.IDEntry = self.xml.get_widget("otherIDEntry")

        self.otherFilterHBox = self.xml.get_widget("otherFilterHBox")
        self.otherFilterHBox.connect("realize", self._populateUI)

        self.vendorEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.interconnectEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())

    def isMember(self, info):
        return info and isOther(info)

class SearchCallbacks(FilteredCallbacks):
    def __init__(self, *args, **kwargs):
        FilteredCallbacks.__init__(self, *args, **kwargs)

        self._ports = []
        self._targets = []
        self._luns = []

        self.filterBy = self.xml.get_widget("searchFilterBy")
        self.notebook = self.xml.get_widget("searchNotebook")

        self.portEntry = self.xml.get_widget("searchPortEntry")
        self.targetEntry = self.xml.get_widget("searchTargetEntry")
        self.LUNEntry = self.xml.get_widget("searchLUNEntry")
        self.IDEntry = self.xml.get_widget("searchIDEntry")

        # When these entries are changed, we need to redo the filtering.
        # If we don't do filter-as-you-type, we'd need a Search/Clear button.
        self.portEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.targetEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.LUNEntry.connect("changed", lambda entry: self.model.get_model().refilter())
        self.IDEntry.connect("changed", lambda entry: self.model.get_model().refilter())

    def isMember(self, info):
        return True

    def visible(self, model, iter, view):
        if not model.get_value(iter, VISIBLE_COL):
            return False

        if self.filtering:
            if self.notebook.get_current_page() == 0:
                return self._visible_by_ptl(model, iter, view)
            else:
                return self._visible_by_wwid(model, iter, view)

        return True

    def _visible_by_ptl(self, model, iter, view):
        rowPort = model.get_value(iter, PORT_COL)
        rowTarget = model.get_value(iter, TARGET_COL)
        rowLUN = model.get_value(iter, LUN_COL)

        enteredPort = self.portEntry.get_text()
        enteredTarget = self.targetEntry.get_text()
        enteredLUN = self.LUNEntry.get_text()

        return (not enteredPort or enteredPort and enteredPort == rowPort) and \
               (not enteredTarget or enteredTarget and enteredTarget == rowTarget) and \
               (not enteredLUN or enteredLUN and enteredLUN == rowLUN)

    def _visible_by_wwid(self, model, iter, view):
        # FIXME:  make this support globs, etc.
        entered = self.IDEntry.get_text()

        return entered != "" and model.get_value(iter, ID_COL).find(entered) != -1

class NotebookPage(object):
    def __init__(self, store, name, xml, cb):
        # Every page needs a ScrolledWindow to display the results in.
        self.scroll = xml.get_widget("%sScroll" % name)

        self.filteredModel = store.filter_new()
        self.sortedModel = gtk.TreeModelSort(self.filteredModel)
        self.treeView = gtk.TreeView(self.sortedModel)

        self.scroll.add(self.treeView)

        self.cb = cb
        self.cb.model = self.sortedModel

        self.ds = DeviceSelector(store, self.sortedModel, self.treeView,
                                 visible=VISIBLE_COL, active=ACTIVE_COL)
        self.ds.createMenu()
        self.ds.createSelectionCol(toggledCB=self.cb.deviceToggled,
                                   membershipCB=self.cb.isMember)

        self.filteredModel.set_visible_func(self.cb.visible, self.treeView)

        # Not every NotebookPage will have a filter box - just those that do
        # some sort of filtering (obviously).
        self.filterBox = xml.get_widget("%sFilterHBox" % name)

        if self.filterBox:
            self.filterBy = xml.get_widget("%sFilterBy" % name)
            self.filterBy.connect("changed", self._filter_by_changed)

            # However if the page has a filter box, then it must also have a
            # notebook with an easily discoverable name.
            self.notebook = xml.get_widget("%sNotebook" % name)

    def _filter_by_changed(self, combo):
        active = combo.get_active()

        if active == -1:
            self.cb.reset()
        else:
            self.cb.set(active)

        self.filteredModel.refilter()

    def getNVisible(self):
        retval = 0
        iter = self.filteredModel.get_iter_first()

        while iter:
            if self.cb.visible(self.filteredModel, iter, self.treeView):
                retval += 1

            iter = self.filteredModel.iter_next(iter)

        return retval

class FilterWindow(InstallWindow):
    windowTitle = N_("Device Filter")

    def getNext(self):
        # All pages use the same store, so we only need to use the first one.
        # However, we do need to make sure all paths from multipath devices
        # are in the list.
        selected = set()
        for dev in self.pages[0].ds.getSelected():
            selected.add(udev_device_get_name(dev[OBJECT_COL]))

        if len(selected) == 0:
            self.anaconda.intf.messageWindow(_("Error"),
                                             _("You must select at least one "
                                               "drive to be used for installation."),
                                             custom_icon="error")
            raise gui.StayOnScreen

        self.anaconda.storage.exclusiveDisks = list(selected)

    def _add_advanced_clicked(self, button):
        from advanced_storage import addDrive

        if not addDrive(self.anaconda):
            return

        udev_trigger(subsystem="block", action="change")
        new_disks = filter(udev_device_is_disk, udev_get_block_devices())
        (new_singlepaths, new_mpaths, new_partitions) = identifyMultipaths(new_disks)
        (new_raids, new_nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d),
                                                    new_singlepaths)

        nonraids = filter(lambda d: d not in self._cachedDevices, new_nonraids)
        mpaths = filter(lambda d: d not in self._cachedMPaths, new_mpaths)
        raids = filter(lambda d: d not in self._cachedRaidDevices, new_raids)

        self.populate(nonraids, mpaths, raids, activeByDefault=True)

        # Make sure to update the size label at the bottom.
        self.pages[0].cb.update()

        self._cachedDevices.extend(nonraids)
        self._cachedMPaths.extend(mpaths)
        self._cachedRaidDevices.extend(raids)

    def _makeBasic(self):
        np = NotebookPage(self.store, "basic", self.xml, Callbacks(self.xml))

        np.ds.addColumn(_("Model"), MODEL_COL)
        np.ds.addColumn(_("Capacity"), CAPACITY_COL)
        np.ds.addColumn(_("Vendor"), VENDOR_COL)
        np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL)
        np.ds.addColumn(_("Serial Number"), SERIAL_COL)
        np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
        return np

    def _makeRAID(self):
        np = NotebookPage(self.store, "raid", self.xml, RAIDCallbacks(self.xml))

        np.ds.addColumn(_("Model"), MODEL_COL)
        np.ds.addColumn(_("Capacity"), CAPACITY_COL)
        np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
        return np

    def _makeMPath(self):
        np = NotebookPage(self.store, "mpath", self.xml, MPathCallbacks(self.xml))

        np.ds.addColumn(_("Identifier"), ID_COL)
        np.ds.addColumn(_("Capacity"), CAPACITY_COL)
        np.ds.addColumn(_("Vendor"), VENDOR_COL)
        np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL)
        np.ds.addColumn(_("Paths"), PATHS_COL)
        np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
        return np

    def _makeOther(self):
        np = NotebookPage(self.store, "other", self.xml, OtherCallbacks(self.xml))

        np.ds.addColumn(_("Identifier"), ID_COL)
        np.ds.addColumn(_("Capacity"), CAPACITY_COL)
        np.ds.addColumn(_("Vendor"), VENDOR_COL)
        np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL)
        np.ds.addColumn(_("Serial Number"), SERIAL_COL, displayed=False)
        np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
        return np

    def _makeSearch(self):
        np = NotebookPage(self.store, "search", self.xml, SearchCallbacks(self.xml))

        np.ds.addColumn(_("Model"), MODEL_COL)
        np.ds.addColumn(_("Capacity"), CAPACITY_COL, displayed=False)
        np.ds.addColumn(_("Vendor"), VENDOR_COL)
        np.ds.addColumn(_("Interconnect"), INTERCONNECT_COL, displayed=False)
        np.ds.addColumn(_("Serial Number"), SERIAL_COL, displayed=False)
        np.ds.addColumn(_("Identifier"), ID_COL)
        np.ds.addColumn(_("Port"), PORT_COL)
        np.ds.addColumn(_("Target"), TARGET_COL)
        np.ds.addColumn(_("LUN"), LUN_COL)
        np.ds.addColumn(_("Device"), DEVICE_COL, displayed=False)
        return np

    def _page_switched(self, notebook, useless, page_num):
        # When the page is switched, we need to change what is visible so the
        # Select All button only selects/deselected things on the current page.
        # Unfortunately, the only way to do this is iterate over all rows and
        # check for membership.
        for line in self.store:
            line[VISIBLE_COL] = self.pages[page_num].cb.isMember(line[OBJECT_COL])

    def _show_buttons(self, *args, **kwargs):
        if self.anaconda.simpleFilter:
            self.buttonBox.hide()
            self.buttonBox.set_no_show_all(True)
        else:
            self.buttonBox.show_all()

    def getScreen(self, anaconda):
        (self.xml, self.vbox) = gui.getGladeWidget("filter.glade", "vbox")
        self.buttonBox = self.xml.get_widget("buttonBox")
        self.notebook = self.xml.get_widget("notebook")
        self.addAdvanced = self.xml.get_widget("addAdvancedButton")

        self.buttonBox.connect("realize", self._show_buttons)
        self.notebook.connect("switch-page", self._page_switched)
        self.addAdvanced.connect("clicked", self._add_advanced_clicked)

        self.pages = []

        self.anaconda = anaconda

        # One common store that all the views on all the notebook tabs share.
        # Yes, this means a whole lot of columns that are going to be empty or
        # unused much of the time.  Oh well.

        # Object,
        # visible, active (checked), immutable,
        # device, model, capacity, vendor, interconnect, serial number, wwid
        # paths, port, target, lun
        self.store = gtk.TreeStore(gobject.TYPE_PYOBJECT,
                                   gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN,
                                   gobject.TYPE_BOOLEAN,
                                   gobject.TYPE_STRING, gobject.TYPE_STRING,
                                   gobject.TYPE_STRING, gobject.TYPE_STRING,
                                   gobject.TYPE_STRING, gobject.TYPE_STRING,
                                   gobject.TYPE_STRING, gobject.TYPE_STRING,
                                   gobject.TYPE_STRING, gobject.TYPE_STRING,
                                   gobject.TYPE_STRING)
        self.store.set_sort_column_id(MODEL_COL, gtk.SORT_ASCENDING)

        udev_trigger(subsystem="block", action="change")
        # So that drives onlined by these show up in the filter UI
        storage.iscsi.iscsi().startup(anaconda.intf)
        storage.fcoe.fcoe().startup(anaconda.intf)
        storage.zfcp.ZFCP().startup()
        # Note we do NOT call dasd.startup() here, that does not online drives,
        # but only checks if they need formatting.
        disks = filter(udev_device_is_disk, udev_get_block_devices())
        (singlepaths, mpaths, partitions) = identifyMultipaths(disks)

        # The device list could be really long, so we really only want to
        # iterate over it the bare minimum of times.  Dividing this list up
        # now means fewer elements to iterate over later.
        (raids, nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d),
                                            singlepaths)

        if anaconda.simpleFilter:
            # In the typical use case, the user likely only has one drive and
            # there's no point showing either the filtering UI or the
            # cleardisks UI.  Unfortunately, that means we need to duplicate
            # some of the getNext method.
            if len(singlepaths) == 1:
                anaconda.storage.exclusiveDisks = [udev_device_get_name(singlepaths[0])]
                return None

            self.pages = [self._makeBasic()]
            self.notebook.set_show_border(False)
            self.notebook.set_show_tabs(False)
        else:
            self.pages = [self._makeBasic(), self._makeRAID(),
                          self._makeMPath(), self._makeOther(),
                          self._makeSearch()]

        self.populate(nonraids, mpaths, raids)

        # If the "Add Advanced" button is ever clicked, we need to have a list
        # of what devices previously existed so we know what's new.  Then we
        # can just add the new devices to the UI.  This is going to be slow,
        # but the user has to click a button to get to the slow part.
        self._cachedDevices = NameCache(singlepaths)
        self._cachedMPaths = NameCache(mpaths)
        self._cachedRaidDevices = NameCache(raids)

        # Switch to the first notebook page that displays any devices.
        i = 0
        for pg in self.pages:
            if pg.getNVisible():
                self.notebook.set_current_page(i)
                break

            i += 1

        return self.vbox

    def populate(self, nonraids, mpaths, raids, activeByDefault=False):
        def _addTuple(tuple):
            global totalDevices, totalSize
            global selectedDevices, selectedSize
            added = False

            self.store.append(None, tuple)

            for pg in self.pages:
                if pg.cb.isMember(tuple[0]):
                    added = True
                    pg.cb.addToUI(tuple)

            # Only update the size label if this device was added to any pages.
            # This prevents situations where we're only displaying the basic
            # filter that has one disk, but there are several advanced disks
            # in the store that cannot be seen.
            if added:
                totalDevices += 1
                totalSize += tuple[0]["XXX_SIZE"]

                if tuple[ACTIVE_COL]:
                    selectedDevices += 1
                    selectedSize += tuple[0]["XXX_SIZE"]

        def _isProtected(info):
            protectedNames = map(udev_resolve_devspec, self.anaconda.protected)

            sysfs_path = udev_device_get_sysfs_path(info)
            for protected in protectedNames:
                _p = "/sys/%s/%s" % (sysfs_path, protected)
                if os.path.exists(os.path.normpath(_p)):
                    return True

            return False

        def _active(info):
            if _isProtected(info) or activeByDefault:
                return True

            name = udev_device_get_name(info)

            if self.anaconda.storage.exclusiveDisks and \
               name in self.anaconda.storage.exclusiveDisks:
                return True
            elif self.anaconda.storage.ignoredDisks and \
                name not in self.anaconda.storage.ignoredDisks:
                return True
            else:
                return False

        for d in nonraids:
            name = udev_device_get_name(d)

            # We aren't guaranteed to be able to get a device.  In
            # particular, built-in USB flash readers show up as devices but
            # do not always have any media present, so parted won't be able
            # to find a device.
            try:
                partedDevice = parted.Device(path="/dev/" + name)
            except (_ped.IOException, _ped.DeviceException):
                continue
            d["XXX_SIZE"] = int(partedDevice.getSize())
            # cciss controllers, without any sets defined, show up as a 0 size
            # blockdev, ignore these
            if d["XXX_SIZE"] == 0:
                continue

            # This isn't so great, but iSCSI and s390 devices have an ID_PATH
            # that contains a lot of useful identifying info, so that should be
            # displayed instead of a blank WWID.
            if udev_device_is_iscsi(d) or udev_device_is_dasd(d) or udev_device_is_zfcp(d):
                ident = udev_device_get_path(d)
            else:
                ident = udev_device_get_wwid(d)

            tuple = (d, True, _active(d), _isProtected(d), name,
                     partedDevice.model, str(d["XXX_SIZE"]) + " MB",
                     udev_device_get_vendor(d), udev_device_get_bus(d),
                     udev_device_get_serial(d), ident, "", "", "", "")
            _addTuple(tuple)

        if raids and flags.dmraid:
            used_raidmembers = []
            for rs in block.getRaidSets():
                # dmraid does everything in sectors
                size = (rs.rs.sectors * 512) / (1024.0 * 1024.0)
                fstype = ""

                # get_members also returns subsets with layered raids, we only
                # want the devices
                members = filter(lambda m: isinstance(m, block.device.RaidDev),
                                 list(rs.get_members()))
                members = map(lambda m: m.get_devpath(), members)
                for d in raids:
                    if udev_device_get_name(d) in members:
                        fstype = udev_device_get_format(d)
                        sysfs_path = udev_device_get_sysfs_path(d)
                        break

                # Skip this set if none of its members are in the raids list
                if not fstype:
                    continue

                used_raidmembers.extend(members)

                # biosraid devices don't really get udev data, at least not in a
                # a way that's useful to the filtering UI.  So we need to fake
                # that data now so we have something to put into the store.
                data = {"XXX_SIZE": size, "ID_FS_TYPE": fstype,
                        "DM_NAME": rs.name, "name": rs.name,
                        "sysfs_path": sysfs_path}

                model = "BIOS RAID set (%s)" % rs.rs.set_type
                tuple = (data, True, _active(data), _isProtected(data), rs.name,
                         model, str(size) + " MB", "", "", "", "", "", "", "", "")
                _addTuple(tuple)

            unused_raidmembers = []
            for d in raids:
                if udev_device_get_name(d) not in used_raidmembers:
                    unused_raidmembers.append(udev_device_get_name(d))

            self.anaconda.intf.unusedRaidMembersWarning(unused_raidmembers)

        for mpath in mpaths:
            # We only need to grab information from the first device in the set.
            name = udev_device_get_name(mpath[0])

            try:
                partedDevice = parted.Device(path="/dev/" + name)
            except (_ped.IOException, _ped.DeviceException):
                continue
            mpath[0]["XXX_SIZE"] = int(partedDevice.getSize())
            model = partedDevice.model

            # However, we do need all the paths making up this multipath set.
            paths = "\n".join(map(udev_device_get_name, mpath))

            # We use a copy here, so as to not modify the original udev info
            # dict as that would break NameCache matching
            data = mpath[0].copy()
            data["name"] = udev_device_get_multipath_name(mpath[0])
            tuple = (data, True, _active(data), _isProtected(data),
                     udev_device_get_multipath_name(mpath[0]), model,
                     str(mpath[0]["XXX_SIZE"]) + " MB",
                     udev_device_get_vendor(mpath[0]),
                     udev_device_get_bus(mpath[0]),
                     udev_device_get_serial(mpath[0]),
                     udev_device_get_wwid(mpath[0]),
                     paths, "", "", "")
            _addTuple(tuple)

    def split_list(self, pred, lst):
        pos = []
        neg = []

        for ele in lst:
            if pred(ele):
                pos.append(ele)
            else:
                neg.append(ele)

        return (pos, neg)
