--- /dev/null
+###############################################################################
+# #
+# Bricklayer - An Installer for IPFire #
+# Copyright (C) 2021 IPFire Development Team #
+# #
+# This program is free software; you can redistribute it and/or #
+# modify it under the terms of the GNU General Public License #
+# as published by the Free Software Foundation; either version 2 #
+# of the License, or (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+import logging
+import parted
+
+from . import step
+from .errors import *
+from .i18n import _
+
+# Setup logging
+log = logging.getLogger("bricklayer.disk")
+
+class Disks(object):
+ """
+ Disks abstraction class
+ """
+ def __init__(self, bricklayer):
+ self.bricklayer = bricklayer
+
+ # Disks
+ self.disks = []
+
+ def scan(self):
+ """
+ Scans for all disks
+ """
+ self.disks.clear()
+
+ log.debug("Scanning for disks...")
+
+ for device in parted.getAllDevices():
+ disk = Disk(self.bricklayer, device)
+
+ # Skip whatever isn't suitable
+ if not disk.supported:
+ continue
+
+ self.disks.append(disk)
+
+ # Sort them alphabetically
+ self.disks.sort()
+
+ @property
+ def supported(self):
+ """
+ The supported disks
+ """
+ return [disk for disk in self.disks if disk.supported]
+
+ @property
+ def selected(self):
+ """
+ The selected disks
+ """
+ return [disk for disk in self.disks if disk.selected]
+
+
+class Disk(object):
+ def __init__(self, bricklayer, device):
+ self.bricklayer = bricklayer
+
+ # The parted device
+ self.device = device
+
+ # Has this device been selected?
+ self.selected = False
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self)
+
+ def __str__(self):
+ return "%s - %s" % (self.model, self.path)
+
+ def __hash__(self):
+ return hash(self.path)
+
+ def __eq__(self, other):
+ if isinstance(other, self.__class__):
+ return self.device == other.device
+
+ return NotImplemented
+
+ def __lt__(self, other):
+ if isinstance(other, self.__class__):
+ return self.model < other.model or self.path < other.path
+
+ return NotImplemented
+
+ @property
+ def supported(self):
+ """
+ Is this device supported?
+ """
+ # We do not support read-only devices
+ if self.device.readOnly:
+ return False
+
+ # Ignore any busy devices
+ if self.device.busy:
+ return False
+
+ return True
+
+ @property
+ def path(self):
+ return self.device.path
+
+ @property
+ def model(self):
+ return self.device.model
+
+
+class SelectDisk(step.Step):
+ """
+ Ask the user which disk(s) to use for the installation process
+ """
+ @property
+ def enabled(self):
+ # Disable in unattended mode
+ return not self.bricklayer.unattended
+
+ def initialize(self):
+ self.disks = self.bricklayer.disks
+
+ # Scan for disks
+ self.disks.scan()
+
+ def run(self, tui):
+ # Create a dictionary with all disks
+ disks = { disk : "%s" % disk for disk in self.disks.supported }
+
+ # Show an error if no suitable disks were found
+ if not disks:
+ tui.error(
+ _("No Disks Found"),
+ _("No supported disks were found")
+ )
+
+ raise InstallAbortedError("No disks found")
+
+ # Get the current selection
+ selection = [disk for disk in disks if disk.selected]
+
+ while True:
+ # Select disks
+ selection = tui.multi_select(
+ _("Disk Selection"),
+ _("Please select all disks for installation"),
+ disks, selection=selection, width=60,
+ )
+
+ # Is at least one disk selected?
+ if not selection:
+ tui.error(
+ _("No Disk Selected"),
+ _("Please select a disk to continue the installation"),
+ buttons=[_("Back")],
+ )
+ continue
+
+ # Apply selection
+ for disk in disks:
+ disk.selected = disk in selection
+
+ break
--- /dev/null
+###############################################################################
+# #
+# Bricklayer - An Installer for IPFire #
+# Copyright (C) 2021 IPFire Development Team #
+# #
+# This program is free software; you can redistribute it and/or #
+# modify it under the terms of the GNU General Public License #
+# as published by the Free Software Foundation; either version 2 #
+# of the License, or (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+class InstallError(Exception):
+ pass
+
+class InstallAbortedError(InstallError):
+ pass
return snack.ButtonChoiceWindow(self.screen, title=title, text=text,
buttons=buttons, help=help)
+ def error(self, title, text, buttons=None):
+ if not buttons:
+ buttons = [_("Abort Installation")]
+
+ return self.message(title, text, buttons=buttons)
+
def progress(self, *args, **kwargs):
return ProgressWindow(self, *args, **kwargs)
# Return the key
return key
+ def multi_select(self, title, text, items, selection=[], buttons=None,
+ width=40, height=None, help=None):
+ assert self.screen
+
+ if height is None:
+ height = len(items)
+
+ # Set some default buttons
+ if buttons is None:
+ buttons = (_("Select"), _("Cancel"))
+
+ button_bar = snack.ButtonBar(self.screen, buttons)
+ text_box = snack.TextboxReflowed(width, text)
+
+ # Create checkboxes
+ checkboxes = snack.CheckboxTree(height, scroll=len(items) > height)
+ for key in items:
+ checkboxes.append(items[key], key, key in selection)
+
+ # Create grid
+ grid = snack.GridFormHelp(self.screen, title, help, 1, 3)
+ grid.add(text_box, 0, 0)
+ grid.add(checkboxes, 0, 1, padding=(0, 1, 0, 1))
+ grid.add(button_bar, 0, 2, growx=True)
+
+ # Run the window
+ rc = grid.runOnce()
+
+ # Return the selection
+ return checkboxes.getSelection()
+
class ProgressWindow(object):
def __init__(self, tui, title, text, max_value=1, width=60, help=None):