2015-06-24 20:22:21 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2015-07-21 15:55:02 +00:00
|
|
|
""" Filesystem
|
|
|
|
|
|
|
|
Provides storage device selection and additional storage
|
|
|
|
configuration.
|
|
|
|
|
|
|
|
"""
|
2015-06-24 20:22:21 +00:00
|
|
|
import logging
|
2015-07-17 00:06:13 +00:00
|
|
|
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter,
|
2015-07-23 02:31:21 +00:00
|
|
|
Text, Columns)
|
2015-06-24 20:22:21 +00:00
|
|
|
from subiquity.ui.lists import SimpleList
|
2015-07-16 21:58:32 +00:00
|
|
|
from subiquity.ui.buttons import done_btn, reset_btn, cancel_btn
|
2015-07-22 13:53:59 +00:00
|
|
|
from subiquity.ui.widgets import Box
|
2015-06-24 20:22:21 +00:00
|
|
|
from subiquity.ui.utils import Padding, Color
|
2015-07-22 18:57:53 +00:00
|
|
|
from subiquity.ui.interactive import (StringEditor, IntegerEditor, Selector)
|
2015-08-10 13:42:19 +00:00
|
|
|
from subiquity.models.filesystem import _humanize_size, _dehumanize_size
|
2015-07-23 02:31:21 +00:00
|
|
|
from subiquity.view import ViewPolicy
|
2015-06-24 20:22:21 +00:00
|
|
|
|
2015-07-21 15:55:02 +00:00
|
|
|
log = logging.getLogger('subiquity.filesystem')
|
|
|
|
|
|
|
|
|
2015-07-17 00:06:13 +00:00
|
|
|
class AddPartitionView(WidgetWrap):
|
|
|
|
|
2015-07-21 20:34:46 +00:00
|
|
|
def __init__(self, model, signal, selected_disk):
|
2015-07-17 00:06:13 +00:00
|
|
|
self.model = model
|
2015-07-22 20:18:18 +00:00
|
|
|
self.signal = signal
|
|
|
|
self.selected_disk = self.model.get_disk(selected_disk)
|
|
|
|
|
2015-08-21 18:36:19 +00:00
|
|
|
self.partnum = IntegerEditor(
|
|
|
|
caption="Partition number: ",
|
|
|
|
default=self.selected_disk.lastpartnumber + 1)
|
2015-08-21 18:07:42 +00:00
|
|
|
self.size_str = _humanize_size(self.selected_disk.freespace)
|
2015-07-22 20:18:18 +00:00
|
|
|
self.size = StringEditor(
|
2015-08-21 18:07:42 +00:00
|
|
|
caption="Size (max {}): ".format(self.size_str))
|
2015-07-22 18:57:53 +00:00
|
|
|
self.mountpoint = StringEditor(caption="Mount: ", edit_text="/")
|
|
|
|
self.fstype = Selector(opts=self.model.supported_filesystems)
|
2015-07-22 02:51:06 +00:00
|
|
|
body = [
|
2015-07-22 14:05:17 +00:00
|
|
|
Padding.center_95(
|
2015-07-22 20:18:18 +00:00
|
|
|
Text("Adding partition to {}".format(
|
|
|
|
self.selected_disk.devpath))),
|
2015-07-22 02:51:06 +00:00
|
|
|
Padding.line_break(""),
|
2015-07-22 14:05:17 +00:00
|
|
|
Padding.center_90(self._container()),
|
2015-07-22 02:51:06 +00:00
|
|
|
Padding.line_break(""),
|
2015-07-22 14:05:17 +00:00
|
|
|
Padding.center_40(self._build_buttons())
|
2015-07-22 02:51:06 +00:00
|
|
|
]
|
2015-07-22 13:53:59 +00:00
|
|
|
partition_box = Padding.center_65(Box(body))
|
|
|
|
super().__init__(partition_box)
|
2015-07-17 00:06:13 +00:00
|
|
|
|
|
|
|
def _build_buttons(self):
|
|
|
|
cancel = cancel_btn(on_press=self.cancel)
|
|
|
|
done = done_btn(on_press=self.done)
|
|
|
|
|
|
|
|
buttons = [
|
|
|
|
Color.button_secondary(cancel, focus_map='button_secondary focus'),
|
|
|
|
Color.button_secondary(done, focus_map='button_secondary focus')
|
|
|
|
]
|
|
|
|
return Pile(buttons)
|
|
|
|
|
|
|
|
def _format_edit(self):
|
2015-07-22 18:57:53 +00:00
|
|
|
formats_list = Pile(self.fstype.group)
|
2015-07-22 02:53:36 +00:00
|
|
|
return Columns([(10, Text("Format: ")), formats_list], 2)
|
2015-07-17 00:06:13 +00:00
|
|
|
|
|
|
|
def _container(self):
|
|
|
|
total_items = [
|
2015-07-22 18:57:53 +00:00
|
|
|
self.partnum,
|
|
|
|
self.size,
|
2015-07-17 00:06:13 +00:00
|
|
|
self._format_edit(),
|
2015-07-22 18:57:53 +00:00
|
|
|
self.mountpoint
|
2015-07-17 00:06:13 +00:00
|
|
|
]
|
2015-07-22 13:57:43 +00:00
|
|
|
return Pile(total_items)
|
2015-07-17 00:06:13 +00:00
|
|
|
|
|
|
|
def cancel(self, button):
|
2015-07-22 02:51:06 +00:00
|
|
|
self.signal.emit_signal('filesystem:show')
|
2015-07-17 00:06:13 +00:00
|
|
|
|
2015-07-22 18:57:53 +00:00
|
|
|
def done(self, result):
|
2015-07-17 00:06:13 +00:00
|
|
|
""" partition spec
|
|
|
|
|
|
|
|
{ 'partition_number': Int,
|
|
|
|
'size': Int(M|G),
|
|
|
|
'format' Str(ext4|btrfs..,
|
|
|
|
'mount_point': Str
|
|
|
|
}
|
|
|
|
"""
|
2015-07-22 18:57:53 +00:00
|
|
|
result = {
|
|
|
|
"partnum": self.partnum.value,
|
2015-07-22 20:18:18 +00:00
|
|
|
"raw_size": self.size.value,
|
|
|
|
"bytes": _dehumanize_size(self.size.value),
|
2015-07-22 18:57:53 +00:00
|
|
|
"fstype": self.fstype.value,
|
|
|
|
"mountpoint": self.mountpoint.value
|
|
|
|
}
|
2015-08-21 18:49:45 +00:00
|
|
|
# Validate mountpoint input
|
|
|
|
if self.mountpoint.value in self.selected_disk.mounts:
|
|
|
|
log.error('provided mountpoint already allocated' +
|
|
|
|
' ({}'.format(self.mountpoint.value))
|
|
|
|
val = self.mountpoint.value
|
|
|
|
# FIXME: update the error message widget instead
|
|
|
|
self.mountpoint.value = 'ERROR: {} already mounted'.format(val)
|
|
|
|
self.signal.emit_signal(
|
|
|
|
'filesystem:add-disk-partiion',
|
|
|
|
self.selected_disk)
|
|
|
|
return
|
|
|
|
# Validate size (bytes) input
|
2015-08-21 18:07:42 +00:00
|
|
|
if self.size.value == self.size_str:
|
|
|
|
log.debug(
|
|
|
|
'User specified max value({}), fixing up: {} -> {}'.format(
|
|
|
|
self.size.value,
|
|
|
|
result['bytes'],
|
|
|
|
int(self.selected_disk.freespace)))
|
|
|
|
result['bytes'] = int(self.selected_disk.freespace)
|
2015-07-22 18:57:53 +00:00
|
|
|
log.debug("Add Partition Result: {}".format(result))
|
2015-07-21 20:34:46 +00:00
|
|
|
self.signal.emit_signal(
|
2015-07-22 20:18:18 +00:00
|
|
|
'filesystem:finish-add-disk-partition',
|
|
|
|
self.selected_disk.devpath, result)
|
2015-07-17 00:06:13 +00:00
|
|
|
|
|
|
|
|
2015-07-16 21:58:32 +00:00
|
|
|
class DiskPartitionView(WidgetWrap):
|
2015-07-21 20:34:46 +00:00
|
|
|
def __init__(self, model, signal, selected_disk):
|
2015-06-24 20:22:21 +00:00
|
|
|
self.model = model
|
2015-07-21 20:34:46 +00:00
|
|
|
self.signal = signal
|
2015-07-16 21:58:32 +00:00
|
|
|
self.selected_disk = selected_disk
|
2015-07-23 01:39:38 +00:00
|
|
|
self.disk_obj = self.model.get_disk(self.selected_disk)
|
|
|
|
|
2015-07-16 21:58:32 +00:00
|
|
|
self.body = [
|
|
|
|
Padding.center_79(self._build_model_inputs()),
|
|
|
|
Padding.line_break(""),
|
|
|
|
Padding.center_79(self._build_menu()),
|
|
|
|
Padding.line_break(""),
|
|
|
|
Padding.center_20(self._build_buttons()),
|
|
|
|
]
|
|
|
|
super().__init__(ListBox(self.body))
|
|
|
|
|
|
|
|
def _build_buttons(self):
|
|
|
|
cancel = cancel_btn(on_press=self.cancel)
|
|
|
|
done = done_btn(on_press=self.done)
|
|
|
|
|
|
|
|
buttons = [
|
|
|
|
Color.button_secondary(cancel, focus_map='button_secondary focus'),
|
|
|
|
Color.button_secondary(done, focus_map='button_secondary focus')
|
|
|
|
]
|
|
|
|
return Pile(buttons)
|
|
|
|
|
|
|
|
def _build_model_inputs(self):
|
2015-07-22 20:18:18 +00:00
|
|
|
partitioned_disks = []
|
|
|
|
|
2015-07-23 01:39:38 +00:00
|
|
|
for mnt, size, fstype, path in self.disk_obj.get_fs_table():
|
2015-07-22 20:18:18 +00:00
|
|
|
mnt = Text(mnt)
|
2015-08-21 16:43:18 +00:00
|
|
|
size = Text("{}".format(_humanize_size(size)))
|
2015-07-22 20:18:18 +00:00
|
|
|
fstype = Text(fstype) if fstype else '-'
|
|
|
|
path = Text(path) if path else '-'
|
|
|
|
partition_column = Columns([
|
|
|
|
(15, path),
|
|
|
|
size,
|
|
|
|
fstype,
|
|
|
|
mnt
|
|
|
|
], 4)
|
|
|
|
partitioned_disks.append(partition_column)
|
2015-08-21 16:43:18 +00:00
|
|
|
free_space = _humanize_size(self.disk_obj.freespace)
|
2015-07-22 20:18:18 +00:00
|
|
|
partitioned_disks.append(Columns([
|
2015-07-23 01:39:38 +00:00
|
|
|
(15, Text("FREE SPACE")),
|
2015-07-22 20:18:18 +00:00
|
|
|
Text(free_space),
|
|
|
|
Text(""),
|
|
|
|
Text("")
|
|
|
|
], 4))
|
|
|
|
|
2015-07-23 01:39:38 +00:00
|
|
|
return BoxAdapter(SimpleList(partitioned_disks, is_selectable=False),
|
2015-07-22 20:18:18 +00:00
|
|
|
height=len(partitioned_disks))
|
2015-07-16 21:58:32 +00:00
|
|
|
|
|
|
|
def _build_menu(self):
|
2015-07-23 01:39:38 +00:00
|
|
|
"""
|
|
|
|
Builds the add partition menu with user visible
|
|
|
|
changes to the button depending on if existing
|
|
|
|
partitions exist or not.
|
|
|
|
"""
|
|
|
|
return Pile([self.add_partition_w(), self.create_swap_w()])
|
|
|
|
|
|
|
|
def create_swap_w(self):
|
|
|
|
""" Handles presenting an enabled create swap on
|
|
|
|
entire device button if no partition exists, otherwise
|
|
|
|
it is disabled.
|
|
|
|
"""
|
2015-07-23 01:44:35 +00:00
|
|
|
text = ("Format or create swap on entire "
|
2015-07-23 01:39:38 +00:00
|
|
|
"device (unusual, advanced)")
|
|
|
|
if len(self.model.get_partitions()) == 0:
|
|
|
|
return Color.button_secondary(done_btn(label=text,
|
|
|
|
on_press=self.create_swap),
|
|
|
|
focus_map='button_secondary focus')
|
|
|
|
return Color.info_minor(Text(text))
|
|
|
|
|
|
|
|
def add_partition_w(self):
|
|
|
|
""" Handles presenting the add partition widget button
|
|
|
|
depending on if partitions exist already or not.
|
|
|
|
"""
|
|
|
|
text = "Add first GPT partition"
|
|
|
|
if len(self.model.get_partitions()) > 0:
|
|
|
|
text = "Add partition (max size {})".format(
|
|
|
|
_humanize_size(self.disk_obj.freespace))
|
|
|
|
return Color.button_secondary(done_btn(label=text,
|
|
|
|
on_press=self.add_partition),
|
|
|
|
focus_map='button_secondary focus')
|
2015-07-16 21:58:32 +00:00
|
|
|
|
2015-07-23 01:39:38 +00:00
|
|
|
def add_partition(self, result):
|
2015-08-21 18:07:42 +00:00
|
|
|
log.debug('add_partition: result={}'.format(result))
|
2015-07-23 01:39:38 +00:00
|
|
|
self.signal.emit_signal('filesystem:add-disk-partition',
|
|
|
|
self.selected_disk)
|
2015-07-22 02:51:06 +00:00
|
|
|
|
2015-07-23 01:39:38 +00:00
|
|
|
def create_swap(self, result):
|
|
|
|
self.signal.emit_signal('filesystem:create-swap-entire-device')
|
2015-07-16 21:58:32 +00:00
|
|
|
|
2015-07-22 02:51:06 +00:00
|
|
|
def done(self, result):
|
2015-07-23 15:04:19 +00:00
|
|
|
''' Return to FilesystemView '''
|
|
|
|
self.signal.emit_signal('filesystem:show')
|
2015-07-16 21:58:32 +00:00
|
|
|
|
|
|
|
def cancel(self, button):
|
2015-07-22 02:51:06 +00:00
|
|
|
self.signal.emit_signal('filesystem:show')
|
2015-07-16 21:58:32 +00:00
|
|
|
|
|
|
|
|
2015-07-23 02:31:21 +00:00
|
|
|
class FilesystemView(ViewPolicy):
|
2015-07-21 20:34:46 +00:00
|
|
|
def __init__(self, model, signal):
|
2015-07-16 21:58:32 +00:00
|
|
|
self.model = model
|
2015-07-21 20:34:46 +00:00
|
|
|
self.signal = signal
|
2015-06-24 20:22:21 +00:00
|
|
|
self.items = []
|
|
|
|
self.body = [
|
2015-07-01 21:20:17 +00:00
|
|
|
Padding.center_79(Text("FILE SYSTEM")),
|
2015-06-30 20:08:16 +00:00
|
|
|
Padding.center_79(self._build_partition_list()),
|
|
|
|
Padding.line_break(""),
|
2015-07-01 21:20:17 +00:00
|
|
|
Padding.center_79(Text("AVAILABLE DISKS")),
|
2015-06-24 20:22:21 +00:00
|
|
|
Padding.center_79(self._build_model_inputs()),
|
|
|
|
Padding.line_break(""),
|
2015-07-16 21:58:32 +00:00
|
|
|
Padding.center_79(self._build_menu()),
|
2015-06-24 20:22:21 +00:00
|
|
|
Padding.line_break(""),
|
2015-07-10 20:58:25 +00:00
|
|
|
self._build_used_disks(),
|
2015-06-24 20:22:21 +00:00
|
|
|
Padding.center_20(self._build_buttons()),
|
|
|
|
]
|
|
|
|
super().__init__(ListBox(self.body))
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def _build_used_disks(self):
|
|
|
|
pl = []
|
|
|
|
for disk in self.model.get_used_disks():
|
|
|
|
pl.append(Text(disk.path))
|
|
|
|
if len(pl):
|
|
|
|
return Padding.center_79(Text("USED DISKS"),
|
|
|
|
Padding.line_break(""),
|
|
|
|
Pile(pl))
|
|
|
|
return Pile(pl)
|
|
|
|
|
2015-06-30 20:08:16 +00:00
|
|
|
def _build_partition_list(self):
|
2015-07-01 21:20:17 +00:00
|
|
|
pl = []
|
2015-06-30 20:08:16 +00:00
|
|
|
if len(self.model.get_partitions()) == 0:
|
|
|
|
pl.append(Color.info_minor(
|
|
|
|
Text("No disks or partitions mounted")))
|
|
|
|
return Pile(pl)
|
2015-07-23 12:32:55 +00:00
|
|
|
for dev in self.model.devices.values():
|
|
|
|
for mnt, size, fstype, path in dev.get_fs_table():
|
|
|
|
mnt = Text(mnt)
|
2015-08-21 16:45:53 +00:00
|
|
|
size = Text("{}".format(_humanize_size(size)))
|
2015-07-23 12:32:55 +00:00
|
|
|
fstype = Text(fstype) if fstype else '-'
|
|
|
|
path = Text(path) if path else '-'
|
|
|
|
partition_column = Columns([
|
|
|
|
(15, path),
|
|
|
|
size,
|
|
|
|
fstype,
|
|
|
|
mnt
|
|
|
|
], 4)
|
|
|
|
pl.append(partition_column)
|
2015-06-30 20:08:16 +00:00
|
|
|
return Pile(pl)
|
|
|
|
|
2015-06-24 20:22:21 +00:00
|
|
|
def _build_buttons(self):
|
|
|
|
buttons = [
|
2015-07-23 15:04:19 +00:00
|
|
|
Color.button_secondary(reset_btn(on_press=self.reset),
|
2015-07-10 20:58:25 +00:00
|
|
|
focus_map='button_secondary focus'),
|
2015-07-23 14:13:19 +00:00
|
|
|
Color.button_secondary(done_btn(on_press=self.done),
|
2015-07-23 12:32:55 +00:00
|
|
|
focus_map='button_secondary focus'),
|
|
|
|
|
2015-06-24 20:22:21 +00:00
|
|
|
]
|
|
|
|
return Pile(buttons)
|
|
|
|
|
2015-08-21 18:07:42 +00:00
|
|
|
def _get_percent_free(self, device):
|
|
|
|
''' return the device free space and percentage
|
|
|
|
of the whole device'''
|
|
|
|
percent = "%d" % (
|
|
|
|
int((1.0 - (device.usedspace / device.size)) * 100))
|
|
|
|
free = _humanize_size(device.freespace)
|
|
|
|
rounded = "{}{}".format(int(float(free[:-1])), free[-1])
|
|
|
|
return (rounded, percent)
|
|
|
|
|
2015-06-24 20:22:21 +00:00
|
|
|
def _build_model_inputs(self):
|
2015-07-01 21:20:17 +00:00
|
|
|
col_1 = []
|
|
|
|
col_2 = []
|
2015-06-25 21:50:13 +00:00
|
|
|
|
2015-07-06 15:55:49 +00:00
|
|
|
for dname in self.model.get_available_disks():
|
|
|
|
disk = self.model.get_disk_info(dname)
|
2015-08-21 18:07:42 +00:00
|
|
|
device = self.model.get_disk(dname)
|
2015-07-16 21:58:32 +00:00
|
|
|
btn = done_btn(label=disk.name,
|
|
|
|
on_press=self.show_disk_partition_view)
|
2015-07-13 15:05:53 +00:00
|
|
|
|
2015-07-01 21:20:17 +00:00
|
|
|
col_1.append(
|
2015-07-13 15:05:53 +00:00
|
|
|
Color.button_primary(btn, focus_map='button_primary focus'))
|
2015-08-21 16:43:18 +00:00
|
|
|
disk_sz = _humanize_size(disk.size)
|
2015-08-21 18:07:42 +00:00
|
|
|
log.debug('device partitions: {}'.format(len(device.partitions)))
|
|
|
|
# if we've consumed some of the device, show
|
|
|
|
# the remaining space and percentage of the whole
|
|
|
|
if len(device.partitions) > 0:
|
|
|
|
free, percent = self._get_percent_free(device)
|
|
|
|
disk_sz = "{} ({}%) free".format(free, percent)
|
2015-07-01 21:20:17 +00:00
|
|
|
col_2.append(Text(disk_sz))
|
|
|
|
|
|
|
|
col_1 = BoxAdapter(SimpleList(col_1),
|
|
|
|
height=len(col_1))
|
|
|
|
col_2 = BoxAdapter(SimpleList(col_2, is_selectable=False),
|
|
|
|
height=len(col_2))
|
2015-07-03 00:53:18 +00:00
|
|
|
return Columns([(15, col_1), col_2], 2)
|
2015-06-24 20:22:21 +00:00
|
|
|
|
2015-07-16 21:58:32 +00:00
|
|
|
def _build_menu(self):
|
2015-06-24 20:22:21 +00:00
|
|
|
opts = []
|
2015-07-22 01:34:46 +00:00
|
|
|
for opt, sig, _ in self.model.get_menu():
|
2015-06-24 20:22:21 +00:00
|
|
|
opts.append(
|
2015-07-21 21:04:28 +00:00
|
|
|
Color.button_secondary(
|
|
|
|
done_btn(label=opt,
|
|
|
|
on_press=self.on_fs_menu_press),
|
|
|
|
focus_map='button_secondary focus'))
|
2015-06-24 20:22:21 +00:00
|
|
|
return Pile(opts)
|
|
|
|
|
2015-07-21 21:04:28 +00:00
|
|
|
def on_fs_menu_press(self, result):
|
|
|
|
self.signal.emit_signal(
|
2015-07-23 02:31:21 +00:00
|
|
|
self.model.get_signal_by_name(result.label))
|
2015-06-24 20:22:21 +00:00
|
|
|
|
|
|
|
def cancel(self, button):
|
2015-07-22 01:34:46 +00:00
|
|
|
self.signal.emit_signal(self.model.get_previous_signal)
|
2015-07-10 20:58:25 +00:00
|
|
|
|
|
|
|
def reset(self, button):
|
2015-07-23 15:04:19 +00:00
|
|
|
self.signal.emit_signal('filesystem:show', True)
|
2015-07-23 12:32:55 +00:00
|
|
|
|
|
|
|
def done(self, button):
|
|
|
|
actions = self.model.get_actions()
|
|
|
|
self.signal.emit_signal('filesystem:finish', False, actions)
|
2015-07-13 15:05:53 +00:00
|
|
|
|
|
|
|
def show_disk_partition_view(self, partition):
|
2015-07-21 20:34:46 +00:00
|
|
|
self.signal.emit_signal('filesystem:show-disk-partition',
|
|
|
|
partition.label)
|