Patch 2 is a little bit scary, but tested.
The rest are fairly simple/safe.
--- pyanaconda/storage/devicetree.py | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py index 52bcacb..9d902aa 100644 --- a/pyanaconda/storage/devicetree.py +++ b/pyanaconda/storage/devicetree.py @@ -513,7 +513,9 @@ class DeviceTree(object): if part.partType and part.isLogical and part.disk == dep.disk: logicals.append(part)
- for device in self.devices: + incomplete = [d for d in self._devices + if not getattr(d, "complete", True)] + for device in self.devices + incomplete: if device.dependsOn(dep): dependents.append(device) else:
--- pyanaconda/storage/__init__.py | 30 +++++++++++++++++++++++------- pyanaconda/storage/devices.py | 16 ++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-)
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index 6bebf6c..883d773 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -1861,6 +1861,9 @@ class Storage(object):
if container: members = container.parents[:] + elif members: + # mdarray + container = device
# The basis for whether we are modifying a member set versus creating # one must be the member list, as container will be None when modifying @@ -1892,12 +1895,15 @@ class Storage(object): for member in members[:]: if any([d in remove_disks for d in member.disks]): if isinstance(member, LUKSDevice): - container.removeMember(member) + if container: + container.removeMember(member) self.destroyDevice(member) members.remove(member) member = member.slave else: - container.removeMember(member) + if container: + container.removeMember(member) + members.remove(member)
self.destroyDevice(member) @@ -1906,19 +1912,24 @@ class Storage(object): if isinstance(member, LUKSDevice): if not factory.encrypted: # encryption was toggled for the member devices - container.removeMember(member) + if container: + container.removeMember(member) + self.destroyDevice(member) members.remove(member)
self.formatDevice(member.slave, getFormat(factory.member_format)) members.append(member.slave) - container.addMember(member.slave) + if container: + container.addMember(member.slave)
member = member.slave elif factory.encrypted: # encryption was toggled for the member devices - container.removeMember(member) + if container: + container.removeMember(member) + members.remove(member) self.formatDevice(member, getFormat("luks")) luks_member = LUKSDevice("luks-%s" % member.name, @@ -1926,7 +1937,8 @@ class Storage(object): format=getFormat(factory.member_format)) self.createDevice(luks_member) members.append(luks_member) - container.addMember(luks_member) + if container: + container.addMember(luks_member)
member.req_base_size = base_size member.req_size = member.req_base_size @@ -2148,7 +2160,7 @@ class Storage(object):
members = [] if device and device.type == "mdarray": - members = device.parents + members = device.parents[:]
try: parents = self.setContainerMembers(container, factory, @@ -3502,6 +3514,10 @@ class MDFactory(DeviceFactory): return get_member_space(self.size, len(self.disks), level=self.raid_level)
+ def container_size_func(self, container, device=None): + return get_member_space(self.size, len(container.parents), + level=self.raid_level) + def new_device(self, *args, **kwargs): kwargs["level"] = self.raid_level kwargs["totalDevices"] = len(kwargs.get("parents")) diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py index f4aabcd..9f796ff 100644 --- a/pyanaconda/storage/devices.py +++ b/pyanaconda/storage/devices.py @@ -2974,6 +2974,22 @@ class MDRaidArrayDevice(StorageDevice): self.devices.remove(device) device.removeChild()
+ def addMember(self, member): + if member in self.parents: + raise ValueError("member is already part of this array") + + # for the time being we will not allow adding members to existing arrays + if self.exists: + raise DeviceError("cannot add member to existing array", self.name) + + self.parents.append(member) + member.addChild() + self.memberDevices += 1 + + def removeMember(self, member): + self._removeDevice(member) + self.memberDevices -= 1 + @property def status(self): """ This device's status.
Related: rhbz#868465 --- pyanaconda/ui/gui/spokes/custom.py | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index f2ab156..08e35e7 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -910,6 +910,9 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): if name_entry.get_sensitive(): name = name_entry.get_text() changed_name = (name != old_name) + else: + # name entry insensitive means we don't control the name + name = None
log.debug("old_name: %s" % old_name) log.debug("new_name: %s" % name)
--- pyanaconda/bootloader.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py index fd68571..8f6306e 100644 --- a/pyanaconda/bootloader.py +++ b/pyanaconda/bootloader.py @@ -1352,7 +1352,8 @@ class GRUB2(GRUB):
# requirements for boot devices stage2_format_types = ["ext4", "ext3", "ext2", "btrfs", "xfs"] - stage2_device_types = ["partition", "mdarray", "lvmlv", "btrfs volume"] + stage2_device_types = ["partition", "mdarray", "lvmlv", "btrfs volume", + "btrfs subvolume"] stage2_raid_levels = [mdraid.RAID0, mdraid.RAID1, mdraid.RAID4, mdraid.RAID5, mdraid.RAID6, mdraid.RAID10]
--- pyanaconda/ui/gui/spokes/custom.glade | 85 +++++++++++++++++++++++++++++++++ pyanaconda/ui/gui/spokes/custom.py | 79 ++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 0 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.glade b/pyanaconda/ui/gui/spokes/custom.glade index c763cf1..465333c 100644 --- a/pyanaconda/ui/gui/spokes/custom.glade +++ b/pyanaconda/ui/gui/spokes/custom.glade @@ -2,6 +2,75 @@ <interface> <!-- interface-requires gtk+ 3.0 --> <!-- interface-requires AnacondaWidgets 1.0 --> + <object class="GtkDialog" id="help_dialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Help</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="type_hint">dialog</property> + <property name="width_request">600</property> + <property name="height_request">400</property> + <signal name="close" handler="on_close" swapped="no"/> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox6"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area6"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="help_close_button"> + <property name="label">gtk-close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTextView" id="help_text_view"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="wrap_mode">word</property> + <property name="cursor_visible">False</property> + <property name="buffer">help_text_buffer</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">help_close_button</action-widget> + </action-widgets> + </object> <object class="GtkDialog" id="addDialog"> <property name="can_focus">False</property> <property name="border_width">5</property> @@ -1665,6 +1734,21 @@ you'll be able to view their details here.</property> <property name="homogeneous">True</property> </packing> </child> + <child> + <object class="GtkToolButton" id="helpButton"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Help</property> + <property name="tooltip_text" translatable="yes">Click for help</property> + <property name="use_underline">True</property> + <property name="icon_name">system-help-symbolic</property> + <signal name="clicked" handler="on_help_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> </object> <packing> <property name="left_attach">0</property> @@ -2276,6 +2360,7 @@ you'll be able to view their details here.</property> <action-widget response="1">select_button</action-widget> </action-widgets> </object> + <object class="GtkTextBuffer" id="help_text_buffer"/> <object class="GtkEntryCompletion" id="mountPointCompletion"> <property name="model">mountPointStore</property> </object> diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 08e35e7..597c90e 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -414,6 +414,20 @@ class VolumeGroupDialog(GUIObject): def run(self): return self.window.run()
+class HelpDialog(GUIObject): + builderObjects = ["help_dialog", "help_text_view", "help_text_buffer"] + mainWidgetName = "help_dialog" + uiFile = "spokes/custom.glade" + + def run(self): + help_text = help_text_template % {"productName": productName} + help_buffer = self.builder.get_object("help_text_buffer") + help_buffer.set_text(help_text) + self.window.run() + + def on_close(self, button): + self.window.destroy() + class CustomPartitioningSpoke(NormalSpoke, StorageChecker): builderObjects = ["customStorageWindow", "sizeAdjustment", "partitionStore", @@ -1990,6 +2004,10 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): showRemove=False) dialog.run()
+ def on_help_clicked(self, button): + help_window = HelpDialog(self.data) + help_window.run() + def on_configure_clicked(self, button): selector = self._current_selector if not selector: @@ -2501,3 +2519,64 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._unused_devices = None # why do we cache this? self._current_selector = None self._do_refresh() + +help_text_template = """You have chosen to manually set up the filesystems for your new %(productName)s installation. Before you begin, you might want to take a minute to learn the lay of the land. Quite a bit has changed. + +The most important change is that creation of new filesystems has been streamlined. You no longer have to build complex devices like LVM logical volumes in stages (physical volume, then volume group, then logical volume) -- now you just create a logical volume and we'll handle the legwork of setting up the physical volumes and volume group to contain it. We'll also handle adjusting the volume group as you add, remove, and resize logical volumes so you don't have to worry about the mundane details. + + +Screen Layout + +The left-hand side of the screen shows the OS installations we were able to find on this computer. The new %(productName)s installation is at the top of the list. You can click on the names of the installations to see what filesystems they contain. + +Below the various installations and mountpoints on the left-hand side there are buttons to add a new filesystem, remove the selected filesystem, or configure the selected filesystem. + +The right-hand side of the screen is where you can customize the currently-selected mountpoint. + +On the bottom-left you will see a summary of the disks you have chosen to use for the installation. You can click on the blue text to see more detailed information about your selected disks. + + +How to create a new filesystem on a new device + +1. Click on the + button. +2. Enter the mountpoint and size. (Hint: Hover the mouse pointer over either of the text entry areas for help.) +3. Select the new mountpoint under "New %(productName)s Installation" on the left-hand side of the screen and customize it to suit your needs. + + +How to reformat a device/filesystem that already exists on your disk + +1. Select the filesystem from the left-hand side of the screen. +2. Click on the "Customize" expander in the mountpoint customization area on the right-hand side of the screen. +3. Activate the "Reformat" checkbutton, select a filesystem type and, if applicable, enter a mountpoint above in the "Mountpoint" text entry area. +4. Click on "Apply changes" + + +How to set a mountpoint for a filesystem that already exists on your disk + +1. Select the filesystem from the left-hand side of the screen. +2. Enter a mountpoint in the "Mountpoint" text entry area in the mountpoint customization area. +3. Click on "Apply changes" + + +How to remove a filesystem that already exists on your disk + +1. Select the filesystem you wish to remove on the left-hand side of the screen. +2. Click the - button. + +Hint: Removing a device that already exists on your disk from the "New %(productName)s Installation" does not remove it from the disk. It only resets that device to its original state. To remove a device that already exists on your disk, you must select it from under any of the other detected installations (or "Unknown") and hit the - button. + + +Tips and hints + +You can enter sizes for new filesystems that are greater than the total available free space. The installer will come as close as possible to the size you request. + +By default, new devices use any/all of your selected disks. + +You can change which disks a new device may be allocated from by clicking the configure button (the one with a tools graphic) while that device is selected. + +When adding a new mountpoint by clicking the + button, leave the size entry blank to make the new device use all available free space. + +When you remove the last device from a container device like an LVM volume group, we will automatically remove that container device to make room for new devices. + +When the last partition is removed from a disk, that disk may be reinitialized with a new partition table if we think there is a more appropriate type for that disk. +"""
--- pyanaconda/ui/gui/spokes/custom.py | 2 +- pyanaconda/ui/gui/spokes/lib/cart.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 597c90e..d9bebfb 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -2001,7 +2001,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
with enlightbox(self.window, dialog.window): dialog.refresh(self._clearpartDevices, self._currentFreeInfo, - showRemove=False) + showRemove=False, setBoot=False) dialog.run()
def on_help_clicked(self, button): diff --git a/pyanaconda/ui/gui/spokes/lib/cart.py b/pyanaconda/ui/gui/spokes/lib/cart.py index 996c577..c1b2aa3 100644 --- a/pyanaconda/ui/gui/spokes/lib/cart.py +++ b/pyanaconda/ui/gui/spokes/lib/cart.py @@ -48,11 +48,11 @@ def size_str(mb): return str(Size(spec=spec)).upper()
class SelectedDisksDialog(GUIObject): - builderObjects = ["selected_disks_dialog", "disk_store"] + builderObjects = ["selected_disks_dialog", "disk_store", "disk_tree_view"] mainWidgetName = "selected_disks_dialog" uiFile = "spokes/lib/cart.glade"
- def initialize(self, disks, free, showRemove=True): + def initialize(self, disks, free, showRemove=True, setBoot=True): self._previousID = None
for disk in disks: @@ -68,6 +68,9 @@ class SelectedDisksDialog(GUIObject): if not showRemove: self.builder.get_object("remove_button").hide()
+ if not setBoot: + self._set_button.hide() + if not disks: return
@@ -93,10 +96,10 @@ class SelectedDisksDialog(GUIObject): row[IS_BOOT_COL] = True break
- def refresh(self, disks, free, showRemove=True): + def refresh(self, disks, free, showRemove=True, setBoot=True): super(SelectedDisksDialog, self).refresh()
- self._view = self.builder.get_object("disk_view") + self._view = self.builder.get_object("disk_tree_view") self._store = self.builder.get_object("disk_store") self._selection = self.builder.get_object("disk_selection") self._summary_label = self.builder.get_object("summary_label") @@ -105,7 +108,7 @@ class SelectedDisksDialog(GUIObject):
# clear out the store and repopulate it from the devicetree self._store.clear() - self.initialize(disks, free, showRemove=showRemove) + self.initialize(disks, free, showRemove=showRemove, setBoot=setBoot)
def run(self): rc = self.window.run()
On Fri, Dec 21, 2012 at 01:57:45PM -0600, David Lehman wrote:
Patch 2 is a little bit scary, but tested.
The rest are fairly simple/safe.
The help_text_template should be translated. Otherwise:
ACK!
anaconda-patches@lists.fedorahosted.org