#
# partition_gui.py: allows the user to choose how to partition their disks
#
# Copyright (C) 2001, 2002 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 .
#
# Author(s): Matt Wilson
# Michael Fulbright
#
import gobject
import gtk
import gtk.glade
try:
import gnomecanvas
except ImportError:
import gnome.canvas as gnomecanvas
import pango
import gui
import parted
import string
import types
import copy
import storage
from iw_gui import *
from flags import flags
import datacombo
import lvm_dialog_gui as l_d_g
import raid_dialog_gui as r_d_g
import partition_dialog_gui as p_d_g
from partIntfHelpers import *
from constants import *
from partition_ui_helpers_gui import *
from storage.partitioning import doPartitioning
from storage.partitioning import hasFreeDiskSpace
from storage.devicelibs import lvm
from storage.devices import devicePathToName, PartitionDevice
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
import logging
log = logging.getLogger("anaconda")
STRIPE_HEIGHT = 35.0
LOGICAL_INSET = 3.0
TREE_SPACING = 2
# XXX hack but will work for now
if gtk.gdk.screen_width() > 640:
CANVAS_WIDTH = 490
else:
CANVAS_WIDTH = 390
CANVAS_HEIGHT = 200
MODE_ADD = 1
MODE_EDIT = 2
class Slice:
"""Class representing a slice of a stripe.
parent -- the stripe that the slice belongs too.
text -- what will appear in the slice
type -- either SLICE or SUBSLICE
xoffset -- start percentage
xlength -- a length percentage
dcCB -- function that is called on a double click.
cCB -- function that is called when one click (selected)
sel_col -- color when selected
unsel_col -- color when unselected
obj -- some python object that is related to this slice.
selected -- initial state of slice.
"""
SLICE = 0
SUBSLICE = 1
CONTAINERSLICE = 2
def __init__(self, parent, text, type, xoffset, xlength, dcCB=lambda: None,
cCB=lambda x: None, sel_col="cornsilk1", unsel_col="white",
obj = None, selected = False):
self.text = text
self.type = type
self.xoffset = xoffset
self.xlength = xlength
self.parent = parent
self.dcCB = dcCB
self.cCB = cCB
self.sel_col = sel_col
self.unsel_col = unsel_col
self.obj = obj
self.selected = selected
def eventHandler(self, widget, event):
if event.type == gtk.gdk.BUTTON_PRESS:
if event.button == 1:
self.select()
self.cCB(self.obj)
elif event.type == gtk.gdk._2BUTTON_PRESS:
#self.select()
self.dcCB()
return True
def putOnCanvas(self):
pgroup = self.parent.getGroup()
self.group = pgroup.add(gnomecanvas.CanvasGroup)
self.box = self.group.add(gnomecanvas.CanvasRect)
self.group.connect("event", self.eventHandler)
canvas_text = self.group.add(gnomecanvas.CanvasText,
font="sans", size_points=8)
xoffset = self.xoffset * CANVAS_WIDTH
xlength = self.xlength * CANVAS_WIDTH
if self.type == Slice.SUBSLICE:
yoffset = 0.0 + LOGICAL_INSET
yheight = STRIPE_HEIGHT - (LOGICAL_INSET * 2)
texty = 0.0
else:
yoffset = 0.0
yheight = STRIPE_HEIGHT
texty = LOGICAL_INSET
if self.selected:
fill_color = self.sel_col
else:
fill_color = self.unsel_col
self.group.set(x=xoffset, y=yoffset)
self.box.set(x1=0.0, y1=0.0, x2=xlength,
y2=yheight, fill_color=fill_color,
outline_color='black', width_units=1.0)
canvas_text.set(x=2.0, y=texty + 2.0, text=self.text,
fill_color='black',
anchor=gtk.ANCHOR_NW, clip=True,
clip_width=xlength-1, clip_height=yheight-1)
def shutDown(self):
self.parent = None
if self.group:
self.group.destroy()
self.group = None
def select(self):
for slice in self.parent.slices:
slice.deselect()
self.selected = True
if self.group and self.box:
if self.type != Slice.CONTAINERSLICE:
self.group.raise_to_top()
self.box.set(outline_color="red")
self.box.set(fill_color=self.sel_col)
def deselect(self):
self.selected = False
if self.box:
self.box.set(outline_color="black", fill_color=self.unsel_col)
class Stripe(object):
"""
canvas -- the canvas where everything goes
text -- the text that will appear on top of the stripe
yoff -- its the position in the y axis where this stripe should be drawn
dcCB -- function that should be called on a double click
obj -- some python object that is related to this stripe
"""
def __init__(self, canvas, text, dcCB, obj = None):
self.canvas_text = None
self.canvas = canvas
self.text = text
self.group = None
self._slices = []
self.dcCB = dcCB
self.selected = None
self.obj = obj
def putOnCanvas(self, yoff):
"""
returns the yposition after drawhing this stripe.
"""
# We set the text for the stripe.
self.canvas_text = self.canvas.root().add(gnomecanvas.CanvasText,
x=0.0, y=yoff, font="sans", size_points=9)
self.canvas_text.set(text=self.text, fill_color='black',
anchor=gtk.ANCHOR_NW, weight=pango.WEIGHT_BOLD)
(xxx1, yyy1, xxx2, yyy2) = self.canvas_text.get_bounds()
textheight = yyy2 - yyy1 + 2
self.group = self.canvas.root().add(gnomecanvas.CanvasGroup,
x=0, y=yoff+textheight)
self.group.add(gnomecanvas.CanvasRect, x1=0.0, y1=0.0, x2=CANVAS_WIDTH,
y2=STRIPE_HEIGHT, fill_color='green',
outline_color='grey71', width_units=1.0)
self.group.lower_to_bottom()
# We paint all the container slices first. So the contained slices
# actually show up.
for slice in [s for s in self.slices if s.type == Slice.CONTAINERSLICE]:
slice.putOnCanvas()
# After painting the containers we paint the rest.
for slice in [s for s in self.slices if s.type != Slice.CONTAINERSLICE]:
slice.putOnCanvas()
# 10 is a separator space.
return yoff + STRIPE_HEIGHT+textheight+10
def shutDown(self):
for slice in self.slices:
slice.shutDown()
self._slices = []
if self.canvas_text:
self.canvas_text.destroy()
if self.group:
self.group.destroy()
self.group = None
def getGroup(self):
return self.group
@property
def slices(self):
return self._slices
def addSlice(self, new_slice):
# check to see if they overlap.
for slice in self.slices:
# Container slices and subslices can overlap.
if new_slice.type+slice.type == Slice.CONTAINERSLICE+Slice.SUBSLICE:
continue
if new_slice.xoffset > slice.xoffset \
and new_slice.xoffset < slice.xoffset + slice.xlength:
# there is a colission, we cannot add.
return
self._slices.append(new_slice)
def getSelectedSlice(self):
for slice in self.slices:
if slice.selected:
return slice
return None
class StripeGraph:
""" This class will only handle one stripe."""
__canvas = None
def __init__(self):
self.stripe = None
self.next_ypos = 0.0
def __del__(self):
self.shutDown()
def shutDown(self):
if self.stripe:
self.stripe.shutDown()
self.stripe = None
self.next_ypos = 0.0
@classmethod
def getCanvas(cls):
if not StripeGraph.__canvas:
StripeGraph.__canvas = gnomecanvas.Canvas()
return StripeGraph.__canvas
def setDisplayed(self, obj):
# Check to see if we already have the correct obj displayed.
if self.getDisplayed() and self.getDisplayed().obj == obj:
return
if self.stripe:
self.stripe.shutDown()
self.stripe = self._createStripe(obj)
self.stripe.putOnCanvas(0)
# Trying to center the picture.
apply(self.getCanvas().set_scroll_region, self.getCanvas().root().get_bounds())
def getDisplayed(self):
return self.stripe
def selectSliceFromObj(self, obj):
"""Search for obj in the slices """
stripe = self.getDisplayed()
if not stripe:
return
for slice in stripe.slices:
# There is a part object in each slice.
if not slice.obj:
continue
if obj == slice.obj and not slice.selected:
slice.select()
break
def _createStripe(self, obj):
#This method needs to be overridden
pass
def getSelectedSlice(self):
return self.stripe.getSelectedSlice()
class DiskStripeGraph(StripeGraph):
"""Handles the creation of a bar view for the 'normal' devies.
storage -- the storage object
cCB -- call back function used when the user clicks on a slice. This function
is passed a device object when its executed.
dcCB -- call back function used when the user double clicks on a slice.
drive -- drive to display
"""
def __init__(self, storage, drive=None, cCB=lambda x:None, dcCB=lambda:None):
StripeGraph.__init__(self)
self.storage = storage
self.cCB = cCB
self.dcCB = dcCB
# Define the default colors per partition type.
self.part_type_colors = \
{"sel_logical": "cornsilk1", "unsel_logical": "white",
"sel_extended": "cornsilk1", "unsel_extended": "white",
"sel_normal": "cornsilk1", "unsel_normal": "white",
"sel_freespace": "grey88", "unsel_freespace": "grey88"}
if drive:
self.setDisplayed(drive)
def _createStripe(self, drive):
# Create the stripe
drivetext = _("Drive %(drive)s (%(size)-0.f MB) (Model: %(model)s)") \
% {'drive': drive.path,
'size': drive.size,
'model': drive.model}
stripe = Stripe(self.getCanvas(), drivetext, self.dcCB, obj = drive)
# Free Extended Calculation
# Free slice/partition in the extended partition "free space". If there
# is space between the last logical partition and the ending of the
# extended partition we create a "free space" in the extended part.
# Create the slices.
# These offsets are where the partition/slices end. 0 (row object)
r_obj = self[iter]["PyObject"]
if obj and r_obj == obj:
# We have fond our object, select this row and break.
selection = self.view.get_selection()
if selection is not None:
selection.unselect_all()
selection.select_iter(iter)
# Make sure the tree view shows what we have selected.
path = self.get_path(iter)
col = self.view.get_column(0)
self.view.set_cursor(path, col, False)
self.view.scroll_to_cell(path, col, True, 0.5, 0.5)
retval = iter
break
if self.iter_has_child(iter):
# Call recursively if row has children.
rv = self.selectRowFromObj(obj, iter=self.iter_children(iter))
if rv != None:
retval = rv
break
iter = self.iter_next(iter)
return iter
def getCurrentDevice(self):
""" Return the device representing the current selection,
None otherwise.
"""
selection = self.view.get_selection()
model, iter = selection.get_selected()
if not iter:
return None
return model[iter]['PyObject']
def getCurrentDeviceParent(self):
""" Return the parent of the selected row. Returns an iter.
None if there is no parent.
"""
selection = self.view.get_selection()
model, iter = selection.get_selected()
if not iter:
return None
return model.iter_parent(iter)
def resetSelection(self):
pass
def clear(self):
selection = self.view.get_selection()
if selection is not None:
selection.unselect_all()
gtk.TreeStore.clear(self)
def __getitem__(self, iter):
if type(iter) == gtk.TreeIter:
return DiskTreeModelHelper(self, self.titleSlot, iter)
raise KeyError, iter
class PartitionWindow(InstallWindow):
def __init__(self, ics):
InstallWindow.__init__(self, ics)
ics.setTitle(_("Partitioning"))
ics.setNextEnabled(True)
self.parent = ics.getICW().window
def quit(self):
pass
def presentPartitioningComments(self,title, labelstr1, labelstr2, comments,
type="ok", custom_buttons=None):
if flags.autostep:
return 1
win = gtk.Dialog(title)
gui.addFrame(win)
if type == "ok":
win.add_button('gtk-ok', 1)
defaultchoice = 0
elif type == "yesno":
win.add_button('gtk-no', 2)
win.add_button('gtk-yes', 1)
defaultchoice = 1
elif type == "continue":
win.add_button('gtk-cancel', 0)
win.add_button(_("Continue"), 1)
defaultchoice = 1
elif type == "custom":
rid=0
for button in custom_buttons:
widget = win.add_button(button, rid)
rid = rid + 1
defaultchoice = rid - 1
image = gtk.Image()
image.set_from_stock('gtk-dialog-warning', gtk.ICON_SIZE_DIALOG)
hbox = gtk.HBox(False, 9)
al=gtk.Alignment(0.0, 0.0)
al.add(image)
hbox.pack_start(al, False)
buffer = gtk.TextBuffer(None)
buffer.set_text(comments)
text = gtk.TextView()
text.set_buffer(buffer)
text.set_property("editable", False)
text.set_property("cursor_visible", False)
text.set_wrap_mode(gtk.WRAP_WORD)
sw = gtk.ScrolledWindow()
sw.add(text)
sw.set_size_request(400, 200)
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.set_shadow_type(gtk.SHADOW_IN)
info1 = gtk.Label(labelstr1)
info1.set_line_wrap(True)
info1.set_size_request(400, -1)
info2 = gtk.Label(labelstr2)
info2.set_line_wrap(True)
info2.set_size_request(400, -1)
vbox = gtk.VBox(False, 9)
al=gtk.Alignment(0.0, 0.0)
al.add(info1)
vbox.pack_start(al, False)
vbox.pack_start(sw, True, True)
al=gtk.Alignment(0.0, 0.0)
al.add(info2)
vbox.pack_start(al, True)
hbox.pack_start(vbox, True, True)
win.vbox.pack_start(hbox)
win.set_position(gtk.WIN_POS_CENTER)
win.set_default_response(defaultchoice)
win.show_all()
rc = win.run()
win.destroy()
return rc
def getNext(self):
(errors, warnings) = self.storage.sanityCheck()
if errors:
labelstr1 = _("The partitioning scheme you requested "
"caused the following critical errors.")
labelstr2 = _("You must correct these errors before "
"you continue your installation of "
"%s.") % (productName,)
commentstr = string.join(errors, "\n\n")
self.presentPartitioningComments(_("Partitioning Errors"),
labelstr1, labelstr2,
commentstr, type="ok")
raise gui.StayOnScreen
if warnings:
# "storage configuration"
labelstr1 = _("The partitioning scheme you requested "
"generated the following warnings.")
labelstr2 = _("Would you like to continue with "
"your requested partitioning "
"scheme?")
commentstr = string.join(warnings, "\n\n")
rc = self.presentPartitioningComments(_("Partitioning Warnings"),
labelstr1, labelstr2,
commentstr,
type="yesno")
if rc != 1:
raise gui.StayOnScreen
formatWarnings = getPreExistFormatWarnings(self.storage)
if formatWarnings:
labelstr1 = _("The following pre-existing devices have been "
"selected to be formatted, destroying all data.")
# labelstr2 = _("Select 'Yes' to continue and format these "
# "partitions, or 'No' to go back and change these "
# "settings.")
labelstr2 = ""
commentstr = ""
for (dev, type, mntpt) in formatWarnings:
commentstr = commentstr + \
"%s %s %s\n" % (dev,type,mntpt)
rc = self.presentPartitioningComments(_("Format Warnings"),
labelstr1, labelstr2,
commentstr,
type="custom",
custom_buttons=["gtk-cancel",
_("_Format")])
if rc != 1:
raise gui.StayOnScreen
self.stripeGraph.shutDown()
self.tree.clear()
del self.parent
return None
def getPrev(self):
self.stripeGraph.shutDown()
# temporarily unset storage.clearPartType so that all devices will be
# found during storage reset
clearPartType = self.storage.clearPartType
self.storage.clearPartType = None
self.storage.reset()
self.storage.clearPartType = clearPartType
self.tree.clear()
del self.parent
return None
def addDevice(self, device, treeiter):
if device.format.hidden:
return
if device.format.type == "luks":
# we'll want to grab format info from the mapped
# device, not the encrypted one
try:
dm_dev = self.storage.devicetree.getChildren(device)[0]
except IndexError:
format = device.format
else:
format = dm_dev.format
else:
format = device.format
# icon for the format column
if device.format.type == "luks" and not device.format.exists:
# we're creating the LUKS header
format_icon = self.lock_pixbuf
elif not format.exists:
# we're creating a format on the device
format_icon = self.checkmark_pixbuf
else:
format_icon = None
# mount point string
if format.type == "lvmpv":
vg = None
for _vg in self.storage.vgs:
if _vg.dependsOn(device):
vg = _vg
break
mnt_str = getattr(vg, "name", "")
elif format.type == "mdmember":
array = None
for _array in self.storage.mdarrays:
if _array.dependsOn(device):
array = _array
break
mnt_str = getattr(array, "name", "")
else:
mnt_str = getattr(format, "mountpoint", "")
if mnt_str is None:
mnt_str = ""
# device name
name_str = getattr(device, "lvname", device.name)
# label
label_str = getattr(format, "label", "")
if label_str is None:
label_str = ""
self.tree[treeiter]['Device'] = name_str
self.tree[treeiter]['Size (MB)'] = "%Ld" % device.size
self.tree[treeiter]['PyObject'] = device
self.tree[treeiter]['IsFormattable'] = format.formattable
self.tree[treeiter]['Format'] = format_icon
self.tree[treeiter]['Mount Point'] = mnt_str
self.tree[treeiter]['IsLeaf'] = True
self.tree[treeiter]['Type'] = format.name
self.tree[treeiter]['Label'] = label_str
def populate(self, initial = 0):
self.tree.resetSelection()
# first do LVM
vgs = self.storage.vgs
if vgs:
lvmparent = self.tree.append(None)
self.tree[lvmparent]['Device'] = _("LVM Volume Groups")
for vg in vgs:
vgparent = self.tree.append(lvmparent)
self.addDevice(vg, vgparent)
self.tree[vgparent]['Type'] = ""
for lv in vg.lvs:
iter = self.tree.append(vgparent)
self.addDevice(lv, iter)
# We add a row for the VG free space.
if vg.freeSpace > 0:
iter = self.tree.append(vgparent)
self.tree[iter]['Device'] = _("Free")
self.tree[iter]['Size (MB)'] = vg.freeSpace
self.tree[iter]['PyObject'] = None
self.tree[iter]['Mount Point'] = ""
self.tree[iter]['IsLeaf'] = True
# handle RAID next
mdarrays = self.storage.mdarrays
if mdarrays:
raidparent = self.tree.append(None)
self.tree[raidparent]['Device'] = _("RAID Devices")
for array in mdarrays:
iter = self.tree.append(raidparent)
self.addDevice(array, iter)
name = "%s (%s)" % \
(array.name, array.path)
self.tree[iter]['Device'] = name
# now normal partitions
disks = self.storage.partitioned
drvparent = self.tree.append(None)
self.tree[drvparent]['Device'] = _("Hard Drives")
for disk in disks:
# add a parent node to the tree
parent = self.tree.append(drvparent)
self.tree[parent]['PyObject'] = disk
if disk.partitioned:
part = disk.format.firstPartition
extendedParent = None
while part:
if part.type & parted.PARTITION_METADATA:
part = part.nextPartition()
continue
partName = devicePathToName(part.getDeviceNodeName())
device = self.storage.devicetree.getDeviceByName(partName)
if not device and not part.type & parted.PARTITION_FREESPACE:
log.debug("can't find partition %s in device"
" tree" % partName)
# ignore the tiny < 1 MB free space partitions (#119479)
if part.getSize(unit="MB") <= 1.0 and \
part.type & parted.PARTITION_FREESPACE:
if not part.active or not device.bootable:
part = part.nextPartition()
continue
if device and device.isExtended:
if extendedParent:
raise RuntimeError, ("can't handle more than "
"one extended partition per disk")
extendedParent = self.tree.append(parent)
iter = extendedParent
elif device and device.isLogical:
if not extendedParent:
raise RuntimeError, ("crossed logical partition "
"before extended")
iter = self.tree.append(extendedParent)
else:
iter = self.tree.append(parent)
if device and not device.isExtended:
self.addDevice(device, iter)
else:
# either extended or freespace
if part.type & parted.PARTITION_FREESPACE:
devstring = _("Free")
ptype = ""
else:
devstring = device.name
ptype = _("Extended")
self.tree[iter]['Device'] = devstring
self.tree[iter]['Type'] = ptype
size = part.getSize(unit="MB")
if size < 1.0:
sizestr = "< 1"
else:
sizestr = "%Ld" % (size)
self.tree[iter]['Size (MB)'] = sizestr
self.tree[iter]['PyObject'] = device
part = part.nextPartition()
else:
# whole-disk formatting
self.addDevice(disk, parent)
# Insert a '\n' when device string is too long. Usually when it
# contains '/dev/mapper'. First column should be around 20 chars.
if len(disk.name) + len(disk.path) > 20:
separator = "\n"
else:
separator= " "
self.tree[parent]['Device'] = \
"%s%s(%s)" \
% (disk.name, separator, disk.path)
self.treeView.expand_all()
self.messageGraph.display()
def barviewActivateCB(self):
""" Should be called when we double click on a slice"""
# This is a bit of a hack to make the double click on free space work.
# This function is useful when the selected slice is a free space,
# in any other case it calls self.treeActiveCB.
# We first see if the double click was from a free space or from another
# slice.
sel_slice = self.stripeGraph.getSelectedSlice()
if sel_slice == None:
# This really should not happen. Do nothing.
return
# The selected slice is a free slice if the object contained in it is
# None.
if sel_slice.obj != None:
# This is not a free slice, we should call treeActivateCB
return self.treeActivateCB()
else:
# Display a create window according to the stripe object.
# Get the device from the stripe.obj
disp_stripe = self.stripeGraph.getDisplayed()
if disp_stripe == None:
# this should not happen
return
# Display a create dialog.
stripe_dev = disp_stripe.obj
if stripe_dev.partitioned:
tempformat = self.storage.defaultFSType
device = self.storage.newPartition(fmt_type=tempformat)
self.editPartition(device, isNew = True)
elif isinstance(stripe_dev, storage.LVMVolumeGroupDevice):
self.editLVMLogicalVolume(vg = stripe_dev)
return
def treeActivateCB(self, *args):
curr_dev = self.tree.getCurrentDevice()
if isinstance(curr_dev, storage.PartitionDevice) \
or isinstance(curr_dev, storage.LVMLogicalVolumeDevice) \
or isinstance(curr_dev, storage.LVMVolumeGroupDevice) \
or isinstance(curr_dev, storage.MDRaidArrayDevice):
self.editCB()
elif curr_dev == None:
# Its probably a free space
iparent = self.tree.getCurrentDeviceParent()
if iparent == None:
# it was not free space, it is a root row.
return
# We execute a create function given the type of parent that was
# found.
# FIXME: This code might repeat itself. might be a good idea to
# put it in a function.
curr_parent = self.tree[iparent]["PyObject"]
if curr_parent.partitioned:
tempformat = self.storage.defaultFSType
device = self.storage.newPartition(fmt_type=tempformat)
self.editPartition(device, isNew = True)
elif isinstance(curr_parent, storage.LVMVolumeGroupDevice):
self.editLVMLogicalVolume(vg = curr_parent)
return
def treeSelectCB(self, selection, *args):
# The edit and create buttons will be enabled if the user has chosen
# something editable and/or deletable.
self.deleteButton.set_sensitive(False)
self.editButton.set_sensitive(False)
# I have no idea why this iter might be None. Its best to return
# without any action.
model, iter = selection.get_selected()
if not iter:
return
# If we return because there is no parent, make sure we show the user
# the infoGraph and no stripeGraph. The 'create' and 'delete' buttons
# will be deactivated.
iparent = model.iter_parent(iter)
if not iparent:
self.stripeGraph.shutDown()
self.messageGraph.display()
return # This is a root row.
# We destroy the message first. We will make sure to repaint it later
# if no stipe is displayed. Can't destroy it at the end of this func
# because it uncenters the created stripe, if any.
self.messageGraph.destroy()
device = model[iter]['PyObject']
# See if we need to change what is in the canvas. In all possibilities
# we must make sure we have the correct StripeGraph class.
if not device:
# This is free space.
parent = self.tree[iparent]["PyObject"]
if parent.partitioned:
if not isinstance(self.stripeGraph, DiskStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = DiskStripeGraph(self.storage,
drive = parent, cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(parent)
elif isinstance(parent, storage.LVMVolumeGroupDevice):
if not isinstance(self.stripeGraph, LVMStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = LVMStripeGraph(self.storage,
vg = parent, cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(parent)
elif device.partitioned:
if not isinstance(self.stripeGraph, DiskStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = DiskStripeGraph(self.storage,
drive = device,
cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(device)
# this is deletable but not editable.
self.deleteButton.set_sensitive(True)
elif isinstance(device, storage.PartitionDevice):
if not isinstance(self.stripeGraph, DiskStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = DiskStripeGraph(self.storage,
drive = device.parents[0],
cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(device.parents[0])
self.stripeGraph.selectSliceFromObj(device)
self.deleteButton.set_sensitive(True)
self.editButton.set_sensitive(True)
elif isinstance(device, storage.LVMVolumeGroupDevice):
if not isinstance(self.stripeGraph, LVMStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = LVMStripeGraph(self.storage, vg = device,
cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(device)
self.deleteButton.set_sensitive(True)
self.editButton.set_sensitive(True)
elif isinstance(device, storage.LVMLogicalVolumeDevice):
if not isinstance(self.stripeGraph, LVMStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = LVMStripeGraph(self.storage, vg = device.vg,
cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(device.vg)
self.stripeGraph.selectSliceFromObj(device)
self.deleteButton.set_sensitive(True)
self.editButton.set_sensitive(True)
elif isinstance(device, storage.MDRaidArrayDevice):
if not isinstance(self.stripeGraph, MDRaidArrayStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = MDRaidArrayStripeGraph(self.storage,
md = device,
cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(device)
self.deleteButton.set_sensitive(True)
self.editButton.set_sensitive(True)
else:
# This means that the user selected something that is not showable
# in the bar view. Just show the information message.
self.stripeGraph.shutDown()
self.messageGraph.display()
self.deleteButton.set_sensitive(False)
self.editButton.set_sensitive(False)
def deleteCB(self, widget):
""" Right now we can say that if the device is partitioned we
want to delete all of the devices it contains. At some point
we will want to support creation and removal of partitionable
devices. This will need some work when that time comes.
"""
device = self.tree.getCurrentDevice()
if device.partitioned:
if doClearPartitionedDevice(self.intf,
self.storage,
device):
self.refresh()
elif doDeleteDevice(self.intf,
self.storage,
device):
if isinstance(device, storage.devices.PartitionDevice):
justRedraw = False
else:
justRedraw = True
if device.type == "lvmlv" and device in device.vg.lvs:
device.vg._removeLogVol(device)
self.refresh(justRedraw=justRedraw)
def createCB(self, *args):
# First we must decide what parts of the create_storage_dialog
# we will activate.
# For the Partition checkboxes.
# If we see that there is free space in the "Hard Drive" list, then we
# must activate all the partition radio buttons (RAID partition,
# LVM partition and Standard partition). We will have only one var to
# control all three activations (Since they all depend on the same
# thing)
activate_create_partition = False
free_part_available = hasFreeDiskSpace(self.storage)
if free_part_available:
activate_create_partition = True
# We activate the create Volume Group radio button if there is a free
# partition with a Physical Volume format.
activate_create_vg = False
availpvs = len(self.storage.unusedPVs())
if (lvm.has_lvm()
and getFormat("lvmpv").supported
and availpvs > 0):
activate_create_vg = True
# We activate the create RAID dev if there are partitions that have
# raid format and are not related to any raid dev.
activate_create_raid_dev = False
availraidparts = len(self.storage.unusedMDMembers())
availminors = self.storage.unusedMDMinors
if (len(availminors) > 0
and getFormat("software RAID").supported
and availraidparts > 1):
activate_create_raid_dev = True
# FIXME: Why do I need availraidparts to clone?
activate_create_raid_clone = False
if (len(self.storage.partitioned) > 1
and availraidparts > 0):
activate_create_raid_clone = True
# Must check if all the possibilities are False. In this case tell the
# user that he can't create anything and the reasons.
if (not activate_create_partition
and not activate_create_vg
and not activate_create_raid_dev
and not activate_create_raid_clone):
self.intf.messageWindow(_("Cannot perform any creation action"),
_("Note that the creation action requires one of the "
"following:\n\n"
"* Free space in one of the Hard Drives.\n"
"* At least two free Software RAID partitions.\n"
"* At least one free physical volume (LVM) partition.\n"
"* At least one Volume Group with free space."),
custom_icon="warning")
return
# We will activate the create lv button when we have a VG to put the
# LVs on.
activate_create_lv = False
vgs_with_free_space = []
for vg in self.storage.vgs:
if vg.freeSpace > 0:
vgs_with_free_space.append(vg)
if len(vgs_with_free_space) > 0:
activate_create_lv = True
# GTK crap starts here.
create_storage_xml = gtk.glade.XML(
gui.findGladeFile("create-storage.glade"), domain="anaconda")
self.dialog = create_storage_xml.get_widget("create_storage_dialog")
# Activate the partition radio buttons if needed.
# sp_rb -> standard partition
sp_rb = create_storage_xml.get_widget("create_storage_rb_standard_part")
# lp_rb -> lvm partition (physical volume)
lp_rb = create_storage_xml.get_widget("create_storage_rb_lvm_part")
# rp_rb -> RAID partition
rp_rb = create_storage_xml.get_widget("create_storage_rb_raid_part")
if activate_create_partition:
sp_rb.set_sensitive(True)
lp_rb.set_sensitive(True)
rp_rb.set_sensitive(True)
# Activate the Volume Group radio buttons if needed.
# vg_rb -> Volume Group
vg_rb = create_storage_xml.get_widget("create_storage_rb_lvm_vg")
if activate_create_vg:
vg_rb.set_sensitive(True)
# Activate the Logical Volume radio button if needed.
# We must also take care to control the combo box.
lv_rb = create_storage_xml.get_widget("create_storage_rb_lvm_lv")
if activate_create_lv:
# The combobox will be visible if the radio button is active.
# The combobox will be sensitive when the radio button is active.
def toggle_vg_cb_CB(button, vg_cb, selected_dev):
if button.get_active():
vg_cb.set_sensitive(True)
# We set the VG to whatever the user has chosen in the tree
# view. We will fall back on the first item on the list if
# there is no chosen VG.
if selected_dev and selected_dev.name \
and vg_cb.set_active_text(selected_dev.name):
# if set_active is True, we don't need to do anything else
pass
else:
vg_cb.set_active_text(vgs_with_free_space[0].name)
else:
vg_cb.set_sensitive(False)
vg_cb_st = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
vg_cb = datacombo.DataComboBox(store = vg_cb_st)
vg_cb.set_sensitive(False)
for vg in vgs_with_free_space:
# FIXME: the name length might be a problem.
vg_cb.append(vg.name, vg)
lv_hb = create_storage_xml.get_widget("create_storage_hb_lvm_lv")
lv_hb.pack_start(vg_cb)
lv_rb.set_sensitive(True)
selected_dev = self.tree.getCurrentDevice()
lv_rb.connect("toggled", toggle_vg_cb_CB, vg_cb, selected_dev)
# Activate the RAID dev if needed.
# rd_rb -> RAID device
rd_rb = create_storage_xml.get_widget("create_storage_rb_raid_dev")
if activate_create_raid_dev:
rd_rb.set_sensitive(True)
# Activate RAID clone if needed.
# rc_rb -> RAID clone
rc_rb = create_storage_xml.get_widget("create_storage_rb_raid_clone")
if activate_create_raid_clone:
rc_rb.set_sensitive(True)
# Before drawing lets select the first radio button that is sensitive:
# How can I get sensitivity from gtk.radiobutton?
if activate_create_partition:
sp_rb.set_active(True)
sp_rb.grab_focus()
elif activate_create_vg:
vg_rb.set_active(True)
vg_rb.grab_focus()
elif activate_create_raid_dev:
rd_rb.set_active(True)
rd_rb.grab_focus()
elif activate_create_raid_clone:
rc_rb.set_active(True)
rc_rb.grab_focus()
gui.addFrame(self.dialog)
self.dialog.show_all()
# Lets work the information messages with CB
# The RAID info message
rinfo_button = create_storage_xml.get_widget("create_storage_info_raid")
whatis_r = _("Software RAID allows you to combine several disks into "
"a larger RAID device. A RAID device can be configured "
"to provide additional speed and reliability compared "
"to using an individual drive. For more information on "
"using RAID devices please consult the %s "
"documentation.\n") % (productName,)
whatneed_r = _("To use RAID you must first create at least two "
"partitions of type 'software RAID'. Then you can create a "
"RAID device that can be formatted and mounted.\n\n")
whathave_r = P_(
"You currently have %d software RAID partition free to use.",
"You currently have %d software RAID partitions free to use.",
availraidparts) % (availraidparts,)
rinfo_message = "%s\n%s%s" % (whatis_r, whatneed_r, whathave_r)
rinfo_cb = lambda x : self.intf.messageWindow(_("About RAID"),
rinfo_message, custom_icon="information")
rinfo_button.connect("clicked", rinfo_cb)
# The LVM info message
lvminfo_button = create_storage_xml.get_widget("create_storage_info_lvm")
whatis_lvm = _("Logical Volume Manager (LVM) is a 3 level construct. "
"The first level is made up of disks or partitions formatted with "
"LVM metadata called Physical Volumes (PV). A Volume Group "
"(VG) sits on top of one or more PVs. The VG, in turn, is the "
"base to create one or more Logical Volumes (LV). Note that a "
"VG can be an aggregate of PVs from multiple physical disks. For "
"more information on using LVM please consult the %s "
"documentation\n") % (productName, )
whatneed_lvm = _("To create a PV you need a partition with "
"free space. To create a VG you need a PV that is not "
"part of any existing VG. To create an LV you need a VG with "
"free space.\n\n")
whathave_lvm = P_("You currently have %d available PV free to use.\n",
"You currently have %d available PVs free to use.\n",
availpvs) % (availpvs, )
if free_part_available:
whathave_lvm = whathave_lvm + _("You currently have free space to "
"create PVs.")
lvminfo_message = "%s\n%s%s" % (whatis_lvm, whatneed_lvm, whathave_lvm)
lvminfo_cb = lambda x : self.intf.messageWindow(_("About LVM"),
lvminfo_message, custom_icon="information")
lvminfo_button.connect("clicked", lvminfo_cb)
dialog_rc = self.dialog.run()
# If Cancel was pressed
if dialog_rc == 0:
self.dialog.destroy()
return
# If Create was pressed Make sure we do a dialog.destroy before
# calling any other screen. We don't want the create dialog to show
# in the back when we pop up other screens.
if dialog_rc != 1:
log.error("I received a dialog_rc != 1 (%d) witch should not "
"happen" % dialog_rc)
self.dialog.destroy()
return
self.dialog.destroy()
if rp_rb.get_active():
member = self.storage.newPartition(fmt_type="mdmember")
self.editPartition(member, isNew = True, restrictfs=["mdmember"])
return
elif rc_rb.get_active():
# r_d_g -> raid_dialog_gui
cloneDialog = r_d_g.RaidCloneDialog(self.storage, self.intf,
self.parent)
if cloneDialog is None:
self.intf.messageWindow(_("Couldn't Create Drive Clone Editor"),
_("The drive clone editor could not "
"be created for some reason."),
custom_icon="error")
return
if cloneDialog.run():
self.refresh()
cloneDialog.destroy()
return
elif rd_rb.get_active():
array = self.storage.newMDArray(fmt_type=self.storage.defaultFSType)
self.editRaidArray(array, isNew = True)
return
elif lp_rb.get_active():
member = self.storage.newPartition(fmt_type="lvmpv")
self.editPartition(member, isNew = True, restrictfs=["lvmpv"])
return
elif vg_rb.get_active():
tempvg = self.storage.newVG()
self.editLVMVolumeGroup(tempvg, isNew = True)
return
elif lv_rb.get_active():
selected_vg = vg_cb.get_active_value()
self.editLVMLogicalVolume(vg = selected_vg)
return
elif sp_rb.get_active():
tempformat = self.storage.defaultFSType
device = self.storage.newPartition(fmt_type=tempformat)
self.editPartition(device, isNew = True)
return
def resetCB(self, *args):
if not confirmResetPartitionState(self.intf):
return
self.stripeGraph.shutDown()
self.storage.reset()
self.tree.clear()
self.populate()
def refresh(self, justRedraw=None):
log.debug("refresh: justRedraw=%s" % justRedraw)
self.stripeGraph.shutDown()
self.tree.clear()
if justRedraw:
rc = 0
else:
try:
doPartitioning(self.storage)
rc = 0
except PartitioningError, msg:
self.intf.messageWindow(_("Error Partitioning"),
_("Could not allocate requested partitions: %s.") % (msg),
custom_icon="error")
rc = -1
except PartitioningWarning, msg:
# XXX somebody other than me should make this look better
# XXX this doesn't handle the 'delete /boot partition spec' case
# (it says 'add anyway')
dialog = gtk.MessageDialog(self.parent, 0, gtk.MESSAGE_WARNING,
gtk.BUTTONS_NONE,
_("Warning: %s.") % (msg))
gui.addFrame(dialog)
button = gtk.Button(_("_Modify Partition"))
dialog.add_action_widget(button, 1)
button = gtk.Button(_("_Continue"))
dialog.add_action_widget(button, 2)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.show_all()
rc = dialog.run()
dialog.destroy()
if rc == 1:
rc = -1
else:
rc = 0
all_devices = self.storage.devicetree.devices
bootDevs = [d for d in all_devices if d.bootable]
#if reqs:
# for req in reqs:
# req.ignoreBootConstraints = 1
if not rc == -1:
self.populate()
return rc
def editCB(self, *args):
device = self.tree.getCurrentDevice()
reason = self.storage.deviceImmutable(device, ignoreProtected=True)
if reason:
self.intf.messageWindow(_("Unable To Edit"),
_("You cannot edit this device:\n\n%s")
% reason,
custom_icon="error")
return
if device.type == "mdarray":
self.editRaidArray(device)
elif device.type == "lvmvg":
self.editLVMVolumeGroup(device)
elif device.type == "lvmlv":
self.editLVMLogicalVolume(lv = device)
elif isinstance(device, storage.devices.PartitionDevice):
self.editPartition(device)
# isNew implies that this request has never been successfully used before
def editRaidArray(self, raiddev, isNew = False):
# r_d_g -> raid_dialog_gui
raideditor = r_d_g.RaidEditor(self.storage, self.intf, self.parent,
raiddev, isNew)
while True:
actions = raideditor.run()
for action in actions:
# FIXME: this needs to handle exceptions
self.storage.devicetree.registerAction(action)
if self.refresh(justRedraw=True):
actions.reverse()
for action in actions:
self.storage.devicetree.cancelAction(action)
if self.refresh():
raise RuntimeError, ("Returning partitions to state "
"prior to RAID edit failed")
continue
else:
break
raideditor.destroy()
def editPartition(self, device, isNew = False, restrictfs = None):
# p_d_g -> partition_dialog_gui
parteditor = p_d_g.PartitionEditor(self.anaconda, self.parent, device,
isNew = isNew, restrictfs = restrictfs)
while True:
orig_device = copy.copy(device)
actions = parteditor.run()
for action in actions:
# XXX we should handle exceptions here
self.anaconda.storage.devicetree.registerAction(action)
if self.refresh(justRedraw=not actions):
# autopart failed -- cancel the actions and try to get
# back to previous state
actions.reverse()
for action in actions:
self.anaconda.storage.devicetree.cancelAction(action)
# FIXME: proper action/device management would be better
if not isNew:
device.req_size = orig_device.req_size
device.req_base_size = orig_device.req_base_size
device.req_grow = orig_device.req_grow
device.req_max_size = orig_device.req_max_size
device.req_primary = orig_device.req_primary
device.req_disks = orig_device.req_disks
if self.refresh():
# this worked before and doesn't now...
raise RuntimeError, ("Returning partitions to state "
"prior to edit failed")
else:
break
parteditor.destroy()
return 1
def editLVMVolumeGroup(self, device, isNew = False):
# l_d_g -> lvm_dialog_gui
vgeditor = l_d_g.VolumeGroupEditor(self.anaconda, self.intf, self.parent,
device, isNew)
while True:
actions = vgeditor.run()
for action in actions:
# FIXME: handle exceptions
self.storage.devicetree.registerAction(action)
if self.refresh(justRedraw=True):
actions.reverse()
for action in actions:
self.storage.devicetree.cancelAction(action)
if self.refresh():
raise RuntimeError, ("Returning partitions to state "
"prior to edit failed")
continue
else:
break
vgeditor.destroy()
def editLVMLogicalVolume (self, lv = None, vg = None):
"""Will be consistent with the state of things and use this funciton
for creating and editing LVs.
lv -- the logical volume to edit. If this is set there is no need
for the other two arguments.
vg -- the volume group where the new lv is going to be created. This
will only be relevant when we are createing an LV.
"""
if lv != None:
# l_d_g -> lvm_dialog_gui
vgeditor = l_d_g.VolumeGroupEditor(self.anaconda, self.intf, self.parent,
lv.vg, isNew = False)
lv = vgeditor.lvs[lv.lvname]
isNew = False
elif vg != None:
# l_d_g -> lvm_dialog_gui
vgeditor = l_d_g.VolumeGroupEditor(self.anaconda, self.intf, self.parent,
vg, isNew = False)
tempvg = vgeditor.getTempVG()
name = self.storage.createSuggestedLVName(tempvg)
format = getFormat(self.storage.defaultFSType)
vgeditor.lvs[name] = {'name': name,
'size': vg.freeSpace,
'format': format,
'originalFormat': format,
'stripes': 1,
'logSize': 0,
'snapshotSpace': 0,
'exists': False}
lv = vgeditor.lvs[name]
isNew = True
else:
# This is non-sense.
return
while True:
vgeditor.editLogicalVolume(lv, isNew = isNew)
actions = vgeditor.convertToActions()
for action in actions:
# FIXME: handle exceptions
self.storage.devicetree.registerAction(action)
if self.refresh(justRedraw=True):
actions.reverse()
for action in actions:
self.storage.devicetree.cancelAction(action)
if self.refresh():
raise RuntimeError, ("Returning partitions to state "
"prior to edit failed")
continue
else:
break
vgeditor.destroy()
def getScreen(self, anaconda):
self.anaconda = anaconda
self.storage = anaconda.storage
self.intf = anaconda.intf
self.checkmark_pixbuf = gui.getPixbuf("checkMark.png")
self.lock_pixbuf = gui.getPixbuf("gnome-lock.png")
checkForSwapNoMatch(anaconda)
# Beginning of the GTK stuff.
# create the operational buttons
buttonBox = gtk.HButtonBox()
buttonBox.set_spacing(6)
buttonBox.set_layout(gtk.BUTTONBOX_END)
ops = ((_("_Create"), self.createCB),
(_("_Edit"), self.editCB),
(_("_Delete"), self.deleteCB),
(_("Re_set"), self.resetCB))
for label, cb in ops:
button = gtk.Button(label)
buttonBox.add (button)
button.connect ("clicked", cb)
# We need these to control their sensitivity.
if label == _("_Edit"):
self.editButton = button
self.editButton.set_sensitive(False)
elif label == _("_Delete"):
self.deleteButton = button
self.deleteButton.set_sensitive(False)
# Create the disk tree (Fills the tree and the Bar View)
self.tree = DiskTreeModel()
self.treeView = self.tree.getTreeView()
self.treeView.connect('row-activated', self.treeActivateCB)
self.treeViewSelection = self.treeView.get_selection()
self.treeViewSelection.connect("changed", self.treeSelectCB)
self.stripeGraph = StripeGraph()
self.messageGraph = MessageGraph(self.stripeGraph.getCanvas(),
_("Please Select A Device"))
self.populate(initial = 1)
# Create the top scroll window
# We don't actually need a *scroll* window but nuthing else worked.
hadj = gtk.Adjustment(step_incr = 5.0)
vadj = gtk.Adjustment(step_incr = 5.0)
swt = gtk.ScrolledWindow(hadjustment = hadj, vadjustment = vadj)
swt.add(self.stripeGraph.getCanvas())
swt.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
swt.set_shadow_type(gtk.SHADOW_IN)
# Create the bottom scroll window
swb = gtk.ScrolledWindow()
swb.add(self.treeView)
swb.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
swb.set_shadow_type(gtk.SHADOW_IN)
# Create main vertical box and add everything.
MVbox = gtk.VBox(False, 5)
MVbox.pack_start(swt, False, False)
MVbox.pack_start(swb, True)
MVbox.pack_start(buttonBox, False, False)
MVbox.pack_start(gtk.HSeparator(), False)
return MVbox