]> git.ipfire.org Git - people/ms/bricklayer.git/commitdiff
Add disk selection using parted
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 5 May 2021 20:57:22 +0000 (20:57 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 5 May 2021 20:57:22 +0000 (20:57 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/python/__init__.py
src/python/disk.py [new file with mode: 0644]
src/python/errors.py [new file with mode: 0644]
src/python/tui/__init__.py

index 1096e9debd4ea3146ea5fa4c2e610cc94c462994..03ceaacd3a688ddb811bf428e4f6834b5f7e46a2 100644 (file)
@@ -45,8 +45,10 @@ dist_bin_SCRIPTS = \
 
 dist_pkgpython_PYTHON = \
        src/python/__init__.py \
+       src/python/disk.py \
+       src/python/errors.py \
        src/python/i18n.py \
-       src/python/lang.py
+       src/python/lang.py \
        src/python/logger.py \
        src/python/step.py
 
index ee3e12234f9a7349251f246c108b2b47a039a6ab..e87e197b95e845ed6563446e78792fd12f1dcdc4 100644 (file)
@@ -20,6 +20,7 @@
 
 import logging
 
+from . import disk
 from . import lang
 from . import logger
 from . import step
@@ -40,6 +41,9 @@ class Bricklayer(object):
                if debug:
                        log.setLevel(logging.DEBUG)
 
+               # Hardware
+               self.disks = disk.Disks(self)
+
                # Initialise the text user interface
                self.tui = tui.Tui(self)
 
@@ -54,6 +58,7 @@ class Bricklayer(object):
                step.UnattendedWarning,
                lang.SelectLanguage,
                step.Welcome,
+               disk.SelectDisk,
        )
 
        def __call__(self):
diff --git a/src/python/disk.py b/src/python/disk.py
new file mode 100644 (file)
index 0000000..7f9fd79
--- /dev/null
@@ -0,0 +1,183 @@
+###############################################################################
+#                                                                             #
+# 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
diff --git a/src/python/errors.py b/src/python/errors.py
new file mode 100644 (file)
index 0000000..cfd1cee
--- /dev/null
@@ -0,0 +1,25 @@
+###############################################################################
+#                                                                             #
+# 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
index cb5aacc9d4860d9dcfeb8affdfa1630b3950c0f8..3edc695fde4b6b3ed666d1548be0c2c48f52ed52 100644 (file)
@@ -104,6 +104,12 @@ class Tui(object):
                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)
 
@@ -129,6 +135,37 @@ class Tui(object):
                # 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):