commit
55532a2362
|
@ -280,7 +280,7 @@ class FilesystemController(BaseController):
|
|||
|
||||
bootable = self.model.bootable()
|
||||
log.debug('model has bootable device? {}'.format(bootable))
|
||||
can_be_boot = disk.supports_action(DeviceAction.MAKE_BOOT)
|
||||
can_be_boot = DeviceAction.MAKE_BOOT in disk.supported_actions
|
||||
if not bootable and len(disk.partitions()) == 0 and can_be_boot:
|
||||
part = self._create_boot_partition(disk)
|
||||
|
||||
|
|
|
@ -162,16 +162,17 @@ def asdict(inst):
|
|||
|
||||
|
||||
class DeviceAction(enum.Enum):
|
||||
INFO = enum.auto()
|
||||
EDIT = enum.auto()
|
||||
PARTITION = enum.auto()
|
||||
FORMAT = enum.auto()
|
||||
DELETE = enum.auto()
|
||||
MAKE_BOOT = enum.auto()
|
||||
INFO = _("Info")
|
||||
EDIT = _("Edit")
|
||||
PARTITION = _("Add Partition")
|
||||
CREATE_LV = _("Create Logical Volume")
|
||||
FORMAT = _("Format")
|
||||
DELETE = _("Delete")
|
||||
MAKE_BOOT = _("Make Boot Device")
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
class _Formattable:
|
||||
class _Formattable(ABC):
|
||||
# Base class for anything that can be formatted and mounted,
|
||||
# e.g. a disk or a RAID or a partition.
|
||||
|
||||
|
@ -189,8 +190,19 @@ class _Formattable:
|
|||
def constructed_device(self):
|
||||
return self._constructed_device
|
||||
|
||||
def supports_action(self, action):
|
||||
return getattr(self, "_supports_" + action.name)
|
||||
@property
|
||||
@abstractmethod
|
||||
def supported_actions(self):
|
||||
pass
|
||||
|
||||
def action_possible(self, action):
|
||||
assert action in self.supported_actions
|
||||
return getattr(self, "_can_" + action.name)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def ok_for_raid(self):
|
||||
pass
|
||||
|
||||
|
||||
# Nothing is put in the first and last megabytes of the disk to allow
|
||||
|
@ -337,18 +349,24 @@ class Disk(_Device):
|
|||
return self.serial
|
||||
return self.path
|
||||
|
||||
_supports_INFO = True
|
||||
_supports_EDIT = False
|
||||
_supports_PARTITION = property(lambda self: self.free_for_partitions > 0)
|
||||
_supports_FORMAT = property(
|
||||
supported_actions = [
|
||||
DeviceAction.INFO,
|
||||
DeviceAction.PARTITION,
|
||||
DeviceAction.FORMAT,
|
||||
DeviceAction.MAKE_BOOT,
|
||||
]
|
||||
_can_INFO = True
|
||||
_can_PARTITION = property(lambda self: self.free_for_partitions > 0)
|
||||
_can_FORMAT = property(
|
||||
lambda self: len(self._partitions) == 0 and
|
||||
self._constructed_device is None)
|
||||
_supports_DELETE = False
|
||||
_supports_MAKE_BOOT = property(
|
||||
_can_MAKE_BOOT = property(
|
||||
lambda self:
|
||||
not self.grub_device and self._fs is None
|
||||
and self._constructed_device is None)
|
||||
|
||||
ok_for_raid = _can_FORMAT
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
class Partition(_Formattable):
|
||||
|
@ -385,15 +403,24 @@ class Partition(_Formattable):
|
|||
def path(self):
|
||||
return "%s%s" % (self.device.path, self._number)
|
||||
|
||||
_supports_INFO = False
|
||||
_supports_EDIT = True
|
||||
_supports_PARTITION = False
|
||||
_supports_FORMAT = property(
|
||||
lambda self: self.flag not in ('boot', 'bios_grub') and
|
||||
self._constructed_device is None)
|
||||
_supports_DELETE = property(
|
||||
supported_actions = [
|
||||
DeviceAction.EDIT,
|
||||
DeviceAction.DELETE,
|
||||
]
|
||||
|
||||
_can_EDIT = True
|
||||
_can_DELETE = property(
|
||||
lambda self: self.flag not in ('boot', 'bios_grub'))
|
||||
_supports_MAKE_BOOT = False
|
||||
|
||||
@property
|
||||
def ok_for_raid(self):
|
||||
if self.flag:
|
||||
return False
|
||||
if self._fs is not None:
|
||||
return False
|
||||
if self._constructed_device is not None:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
|
@ -417,19 +444,35 @@ class Raid(_Device):
|
|||
def desc(self):
|
||||
return _("software RAID {}").format(self.raidlevel)
|
||||
|
||||
_supports_INFO = False
|
||||
_supports_EDIT = True
|
||||
_supports_PARTITION = Disk._supports_PARTITION
|
||||
_supports_FORMAT = property(
|
||||
supported_actions = [
|
||||
DeviceAction.EDIT,
|
||||
DeviceAction.PARTITION,
|
||||
DeviceAction.FORMAT,
|
||||
DeviceAction.DELETE,
|
||||
]
|
||||
|
||||
_can_EDIT = True
|
||||
_can_PARTITION = Disk._can_PARTITION
|
||||
_can_FORMAT = property(
|
||||
lambda self: len(self._partitions) == 0 and
|
||||
self._constructed_device is None)
|
||||
_supports_DELETE = True
|
||||
_supports_MAKE_BOOT = False
|
||||
_can_DELETE = True
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return "/dev/{}".format(self.name)
|
||||
|
||||
@property
|
||||
def ok_for_raid(self):
|
||||
if self._fs is not None:
|
||||
return False
|
||||
if self._constructed_device is not None:
|
||||
return False
|
||||
return True
|
||||
|
||||
# What is a device that makes up this device referred to as?
|
||||
component_name = "component"
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
class Filesystem:
|
||||
|
|
|
@ -32,7 +32,7 @@ from urwid import (
|
|||
from subiquitycore.ui.actionmenu import (
|
||||
Action,
|
||||
ActionMenu,
|
||||
ActionMenuButton,
|
||||
ActionMenuOpenButton,
|
||||
)
|
||||
from subiquitycore.ui.buttons import (
|
||||
back_btn,
|
||||
|
@ -62,7 +62,10 @@ from subiquitycore.ui.utils import (
|
|||
)
|
||||
from subiquitycore.view import BaseView
|
||||
|
||||
from subiquity.models.filesystem import DeviceAction, humanize_size
|
||||
from subiquity.models.filesystem import (
|
||||
DeviceAction,
|
||||
humanize_size,
|
||||
)
|
||||
|
||||
from .delete import can_delete, ConfirmDeleteStretchy
|
||||
from .disk_info import DiskInfoStretchy
|
||||
|
@ -173,7 +176,6 @@ class MountList(WidgetWrap):
|
|||
self._no_mounts_content = Color.info_minor(
|
||||
Text(_("No disks or partitions mounted.")))
|
||||
super().__init__(self.table)
|
||||
self.refresh_model_inputs()
|
||||
|
||||
def _mount_action(self, sender, action, mount):
|
||||
log.debug('_mount_action %s %s', action, mount)
|
||||
|
@ -251,6 +253,7 @@ class MountList(WidgetWrap):
|
|||
def _stretchy_shower(cls):
|
||||
def impl(self, device):
|
||||
self.parent.show_stretchy_overlay(cls(self.parent, device))
|
||||
impl.opens_dialog = True
|
||||
return impl
|
||||
|
||||
|
||||
|
@ -272,9 +275,6 @@ class DeviceList(WidgetWrap):
|
|||
text = _("No used devices")
|
||||
self._no_devices_content = Color.info_minor(Text(text))
|
||||
super().__init__(self.table)
|
||||
self.refresh_model_inputs()
|
||||
# I don't really know why this is required:
|
||||
self.table._select_first_selectable()
|
||||
|
||||
_disk_INFO = _stretchy_shower(DiskInfoStretchy)
|
||||
_disk_PARTITION = _stretchy_shower(PartitionStretchy)
|
||||
|
@ -287,7 +287,6 @@ class DeviceList(WidgetWrap):
|
|||
_partition_EDIT = _stretchy_shower(
|
||||
lambda parent, part: PartitionStretchy(parent, part.device, part))
|
||||
_partition_DELETE = _stretchy_shower(ConfirmDeleteStretchy)
|
||||
_partition_FORMAT = _disk_FORMAT
|
||||
|
||||
_raid_EDIT = _stretchy_shower(RaidStretchy)
|
||||
_raid_PARTITION = _disk_PARTITION
|
||||
|
@ -300,26 +299,29 @@ class DeviceList(WidgetWrap):
|
|||
getattr(self, meth_name)(device)
|
||||
|
||||
def _action_menu_for_device(self, device):
|
||||
if can_delete(device)[0]:
|
||||
delete_btn = Color.danger_button(ActionMenuButton(_("Delete")))
|
||||
else:
|
||||
delete_btn = _("Delete *")
|
||||
device_actions = [
|
||||
(_("Information"), DeviceAction.INFO),
|
||||
(_("Edit"), DeviceAction.EDIT),
|
||||
(_("Add Partition"), DeviceAction.PARTITION),
|
||||
(_("Format / Mount"), DeviceAction.FORMAT),
|
||||
(delete_btn, DeviceAction.DELETE),
|
||||
(_("Make boot device"), DeviceAction.MAKE_BOOT),
|
||||
]
|
||||
actions = []
|
||||
for label, action in device_actions:
|
||||
actions.append(Action(
|
||||
device_actions = []
|
||||
can_delete_device = can_delete(device)[0]
|
||||
for action in device.supported_actions:
|
||||
if action == DeviceAction.DELETE:
|
||||
enabled = True
|
||||
if can_delete_device:
|
||||
label = Color.danger_button(
|
||||
ActionMenuOpenButton(_("Delete")))
|
||||
else:
|
||||
label = _("Delete *")
|
||||
else:
|
||||
label = _(action.value)
|
||||
enabled = device.action_possible(action)
|
||||
meth_name = '_{}_{}'.format(device.type, action.name)
|
||||
meth = getattr(self, meth_name)
|
||||
opens_dialog = getattr(meth, 'opens_dialog', False)
|
||||
device_actions.append(Action(
|
||||
label=label,
|
||||
enabled=device.supports_action(action),
|
||||
enabled=enabled,
|
||||
value=action,
|
||||
opens_dialog=action != DeviceAction.MAKE_BOOT))
|
||||
menu = ActionMenu(actions, "\N{BLACK RIGHT-POINTING SMALL TRIANGLE}")
|
||||
opens_dialog=opens_dialog))
|
||||
menu = ActionMenu(
|
||||
device_actions, "\N{BLACK RIGHT-POINTING SMALL TRIANGLE}")
|
||||
connect_signal(menu, 'action', self._action, device)
|
||||
return menu
|
||||
|
||||
|
@ -337,20 +339,23 @@ class DeviceList(WidgetWrap):
|
|||
log.debug('FileSystemView: building device list')
|
||||
rows = []
|
||||
|
||||
def _fmt_fs(label, fs):
|
||||
r = _("{} {}").format(label, fs.fstype)
|
||||
if not self.parent.model.fs_by_name[fs.fstype].is_mounted:
|
||||
return r
|
||||
m = fs.mount()
|
||||
if m:
|
||||
r += _(", {}").format(m.path)
|
||||
def _usage_label(obj):
|
||||
cd = obj.constructed_device()
|
||||
if cd is not None:
|
||||
return _("{component_name} of {name}").format(
|
||||
component_name=cd.component_name, name=cd.name)
|
||||
fs = obj.fs()
|
||||
if fs is not None:
|
||||
m = fs.mount()
|
||||
if m:
|
||||
return _(
|
||||
"formatted as {fstype}, mounted at {path}").format(
|
||||
fstype=fs.fstype, path=m.path)
|
||||
else:
|
||||
return _("formatted as {fstype}, not mounted").format(
|
||||
fstype=fs.fstype)
|
||||
else:
|
||||
r += _(", not mounted")
|
||||
return r
|
||||
|
||||
def _fmt_constructed(label, device):
|
||||
return _("{} part of {} ({})").format(
|
||||
label, device.label, device.desc())
|
||||
return _("unused")
|
||||
|
||||
rows.append(TableRow([Color.info_minor(heading) for heading in [
|
||||
Text(" "),
|
||||
|
@ -374,19 +379,10 @@ class DeviceList(WidgetWrap):
|
|||
menu, row, 'menu_button', 'menu_button focus')
|
||||
rows.append(row)
|
||||
|
||||
entire_label = None
|
||||
if device.fs():
|
||||
entire_label = _fmt_fs(
|
||||
" " + _("entire device formatted as"),
|
||||
device.fs())
|
||||
elif device.constructed_device():
|
||||
entire_label = _fmt_constructed(
|
||||
" " + _("entire device"),
|
||||
device.constructed_device())
|
||||
if entire_label is not None:
|
||||
if not device.partitions():
|
||||
rows.append(TableRow([
|
||||
Text(""),
|
||||
(3, Text(entire_label)),
|
||||
(3, Text(" " + _usage_label(device))),
|
||||
Text(""),
|
||||
Text(""),
|
||||
]))
|
||||
|
@ -394,23 +390,13 @@ class DeviceList(WidgetWrap):
|
|||
for part in device.partitions():
|
||||
if part.available() != self.show_available:
|
||||
continue
|
||||
prefix = _("partition {},").format(part._number)
|
||||
if part.flag == "bios_grub":
|
||||
label = prefix + " bios_grub"
|
||||
elif part.fs():
|
||||
label = _fmt_fs(prefix, part.fs())
|
||||
elif part.constructed_device():
|
||||
label = _fmt_constructed(
|
||||
prefix, part.constructed_device())
|
||||
else:
|
||||
label = _("{} not formatted").format(prefix)
|
||||
menu = self._action_menu_for_device(part)
|
||||
part_size = "{:>9} ({}%)".format(
|
||||
humanize_size(part.size),
|
||||
int(100 * part.size / device.size))
|
||||
menu = self._action_menu_for_device(part)
|
||||
row = TableRow([
|
||||
Text("["),
|
||||
Text(" " + label),
|
||||
Text(" " + _("partition {}").format(part._number)),
|
||||
(2, Text(part_size)),
|
||||
menu,
|
||||
Text("]"),
|
||||
|
@ -419,6 +405,16 @@ class DeviceList(WidgetWrap):
|
|||
menu, row, 'menu_button', 'menu_button focus',
|
||||
cursor_x=4)
|
||||
rows.append(row)
|
||||
if part.flag == "bios_grub":
|
||||
label = "bios_grub"
|
||||
else:
|
||||
label = _usage_label(part)
|
||||
rows.append(TableRow([
|
||||
Text(""),
|
||||
(3, Text(" " + label)),
|
||||
Text(""),
|
||||
Text(""),
|
||||
]))
|
||||
if (self.show_available
|
||||
and device.used > 0
|
||||
and device.free_for_partitions > 0):
|
||||
|
@ -456,9 +452,9 @@ class FilesystemView(BaseView):
|
|||
self.avail_list = DeviceList(self, True)
|
||||
self.used_list = DeviceList(self, False)
|
||||
self.avail_list.table.bind(self.used_list.table)
|
||||
self._create_raid_btn = menu_btn(
|
||||
self._create_raid_btn = Toggleable(menu_btn(
|
||||
label=_("Create software RAID (md)"),
|
||||
on_press=self.create_raid)
|
||||
on_press=self.create_raid))
|
||||
|
||||
bp = button_pile([self._create_raid_btn])
|
||||
bp.align = 'left'
|
||||
|
@ -488,13 +484,12 @@ class FilesystemView(BaseView):
|
|||
focus_buttons=self.model.can_install())
|
||||
frame.width = ('relative', 95)
|
||||
super().__init__(frame)
|
||||
self.refresh_model_inputs()
|
||||
log.debug('FileSystemView init complete()')
|
||||
|
||||
def _build_buttons(self):
|
||||
log.debug('FileSystemView: building buttons')
|
||||
self.done = Toggleable(done_btn(_("Done"), on_press=self.done))
|
||||
if not self.model.can_install():
|
||||
self.done.disable()
|
||||
|
||||
return [
|
||||
self.done,
|
||||
|
@ -503,18 +498,20 @@ class FilesystemView(BaseView):
|
|||
]
|
||||
|
||||
def refresh_model_inputs(self):
|
||||
raid_devices = set()
|
||||
for d in self.model.all_devices():
|
||||
if d.ok_for_raid:
|
||||
raid_devices.add(d)
|
||||
for p in d.partitions():
|
||||
if p.ok_for_raid:
|
||||
raid_devices.add(p)
|
||||
self._create_raid_btn.enabled = len(raid_devices) > 1
|
||||
self.mount_list.refresh_model_inputs()
|
||||
self.avail_list.refresh_model_inputs()
|
||||
self.used_list.refresh_model_inputs()
|
||||
# If refreshing the view has left the focus widget with no
|
||||
# selectable widgets, simulate a tab to move to the next
|
||||
# selectable widget.
|
||||
while not self.lb.base_widget.focus.selectable():
|
||||
self.lb.base_widget.keypress((10, 10), 'tab')
|
||||
if self.model.can_install():
|
||||
self.done.enable()
|
||||
else:
|
||||
self.done.disable()
|
||||
# This is an awful hack, actual thinking required:
|
||||
self.lb.base_widget._select_first_selectable()
|
||||
self.done.enabled = self.model.can_install()
|
||||
|
||||
def create_raid(self, button=None):
|
||||
self.show_stretchy_overlay(RaidStretchy(self))
|
||||
|
|
|
@ -54,7 +54,6 @@ from subiquitycore.ui.utils import (
|
|||
from .partition import FSTypeField
|
||||
from subiquity.ui.mount import MountField
|
||||
from subiquity.models.filesystem import (
|
||||
DeviceAction,
|
||||
get_raid_size,
|
||||
humanize_size,
|
||||
raidlevels,
|
||||
|
@ -90,10 +89,10 @@ class MultiDeviceChooser(WidgetWrap, WantsToKnowFormField):
|
|||
self.devices = value
|
||||
for d, s in self.device_to_selector.items():
|
||||
if d in self.devices:
|
||||
s.enable()
|
||||
s.enabled = True
|
||||
s.base_widget.value = self.devices[d]
|
||||
else:
|
||||
s.disable()
|
||||
s.enabled = False
|
||||
for d, b in self.device_to_checkbox.items():
|
||||
b.set_state(d in self.devices)
|
||||
|
||||
|
@ -113,24 +112,24 @@ class MultiDeviceChooser(WidgetWrap, WantsToKnowFormField):
|
|||
self.supports_spares = val
|
||||
if val:
|
||||
for device in list(self.devices):
|
||||
self.device_to_selector[device].enable()
|
||||
selector = self.device_to_selector[device]
|
||||
selector.enabled = True
|
||||
self.devices[device] = selector.base_widget.value
|
||||
self.table.set_contents(self.all_rows)
|
||||
else:
|
||||
for device in list(self.devices):
|
||||
self.device_to_selector[device].disable()
|
||||
self.device_to_selector[device].enabled = False
|
||||
self.devices[device] = 'active'
|
||||
self.table.set_contents(self.no_selector_rows)
|
||||
|
||||
def _state_change_device(self, sender, state, device):
|
||||
if state:
|
||||
if self.supports_spares:
|
||||
self.device_to_selector[device].enable()
|
||||
self.device_to_selector[device].enabled = True
|
||||
selector = self.device_to_selector[device]
|
||||
self.devices[device] = selector.base_widget.value
|
||||
else:
|
||||
self.device_to_selector[device].disable()
|
||||
self.device_to_selector[device].enabled = False
|
||||
del self.devices[device]
|
||||
self._emit('change', self.devices)
|
||||
|
||||
|
@ -188,7 +187,7 @@ class MultiDeviceChooser(WidgetWrap, WantsToKnowFormField):
|
|||
UrwidPadding(
|
||||
Color.menu_button(selector),
|
||||
left=len(prefix)))
|
||||
selector.disable()
|
||||
selector.enabled = False
|
||||
self.device_to_selector[device] = selector
|
||||
self.all_rows.append(TableRow([(2, selector)]))
|
||||
# Do not append that one to no_selector_rows!
|
||||
|
@ -341,9 +340,11 @@ class RaidStretchy(Stretchy):
|
|||
cur_devices = existing.devices | existing.spare_devices
|
||||
|
||||
def device_ok(dev):
|
||||
return (dev not in omits
|
||||
and (dev.supports_action(DeviceAction.FORMAT)
|
||||
or dev in cur_devices))
|
||||
if dev in omits:
|
||||
return False
|
||||
if dev in cur_devices:
|
||||
return True
|
||||
return dev.ok_for_raid
|
||||
|
||||
for dev in self.parent.model.all_devices():
|
||||
if device_ok(dev):
|
||||
|
@ -375,7 +376,8 @@ class RaidStretchy(Stretchy):
|
|||
Text("You cannot save edit to RAIDs just yet."),
|
||||
Text(""),
|
||||
]
|
||||
self.form.validated = lambda *args: self.form.done_btn.disable()
|
||||
self.form.validated = lambda *args: setattr(
|
||||
self.form.done_btn, 'enabled', False)
|
||||
self.form.validated()
|
||||
|
||||
super().__init__(
|
||||
|
|
|
@ -58,18 +58,20 @@ class Toggleable(delegate_to_widget_mixin('_original_widget'),
|
|||
|
||||
def __init__(self, original):
|
||||
self.original = original
|
||||
self.enabled = False
|
||||
self.enable()
|
||||
self._enabled = False
|
||||
self.enabled = True
|
||||
|
||||
def enable(self):
|
||||
if not self.enabled:
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, val):
|
||||
if val and not self._enabled:
|
||||
self.original_widget = self.original
|
||||
self.enabled = True
|
||||
|
||||
def disable(self):
|
||||
if self.enabled:
|
||||
elif not val and self._enabled:
|
||||
self.original_widget = disabled(self.original)
|
||||
self.enabled = False
|
||||
self._enabled = val
|
||||
|
||||
|
||||
class _Validator(WidgetWrap):
|
||||
|
@ -244,14 +246,9 @@ class BoundFormField(object):
|
|||
|
||||
@enabled.setter
|
||||
def enabled(self, val):
|
||||
if val != self._enabled:
|
||||
self._enabled = val
|
||||
if val:
|
||||
for row in self._rows:
|
||||
row.enable()
|
||||
else:
|
||||
for row in self._rows:
|
||||
row.disable()
|
||||
self._enabled = val
|
||||
for row in self._rows:
|
||||
row.enabled = val
|
||||
|
||||
|
||||
def simple_field(widget_maker):
|
||||
|
@ -401,10 +398,10 @@ class Form(object, metaclass=MetaForm):
|
|||
in_error = True
|
||||
break
|
||||
if in_error:
|
||||
self.buttons.base_widget.contents[0][0].disable()
|
||||
self.buttons.base_widget.contents[0][0].enabled = False
|
||||
self.buttons.base_widget.focus_position = 1
|
||||
else:
|
||||
self.buttons.base_widget.contents[0][0].enable()
|
||||
self.buttons.base_widget.contents[0][0].enabled = True
|
||||
|
||||
def as_data(self):
|
||||
data = {}
|
||||
|
|
Loading…
Reference in New Issue