qubes-installer-qubes-os/anaconda/pyanaconda/ui/helpers.py
Marek Marczykowski-Górecki 6bc5671491
anaconda: update to 25.20.9-1
Apply:
  git diff --full-index --binary anaconda-23.19.10-1..anaconda-25.20.9-1

And resolve conflicts.

QubesOS/qubes-issues#2574
2017-02-14 02:36:20 +01:00

390 lines
15 KiB
Python

# Abstract base classes for UI classes
#
# Copyright (C) 2013 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# This file contains abstract base classes that provide specific functionality
# that can be added to another class. The idea is sort-of modelled after Java's
# interfaces. An abstract base class cannot be instatiated, and it provides a
# contract for classes that inherit from it: any method or property marked as
# abstract in the base class must be overriden in the inheriting class. This
# allows for cleaner implementation of certain types of mixin-classes: a class
# that adds functionality to another class can explicitly require that methods
# or properties be provided by the inheriting class or another superclass of
# the inheriting class.
#
# In general, classes that inherit from abstract base classes should place the
# abstract base class at the end of the inheritance list. This way any abstract
# methods or properties in the abc will be overridden by the base classes
# that are first in the inheritance list. For example, an abstract base class
# may add a method that reads from Spoke.data:
#
# class Mixin(object):
# __metaclass__ = ABCMeta
#
# @abstractproperty
# def data(self):
# pass
#
# def isHD(self):
# return self.data.method == "harddrive"
#
# The Mixin class will add the method isHD to any class that inherits from it,
# and classes that inherit from Mixin must provide a data property.
#
# class MixedObject(UIObject, Mixin):
# ....
#
# The method resolution order of MixedObject resolves UIObject.data before
# Mixin.data, so UIObject.data satisfies the requirment that Mixin.data be
# overriden.
from abc import ABCMeta, abstractproperty, abstractmethod
from pyanaconda import constants
from pyanaconda.threads import threadMgr, AnacondaThread
from pyanaconda.ui.communication import hubQ
from pyanaconda.i18n import _
from pyanaconda.packaging import payloadMgr
from pyanaconda import isys
import logging
import copy
class StorageChecker(object, metaclass=ABCMeta):
log = logging.getLogger("anaconda")
errors = []
warnings = []
def __init__(self, min_ram=isys.MIN_RAM, mainSpokeClass="StorageSpoke"):
self._mainSpokeClass = mainSpokeClass
self._min_ram = min_ram
@abstractproperty
def storage(self):
pass
def run(self):
threadMgr.add(AnacondaThread(name=constants.THREAD_CHECK_STORAGE,
target=self.checkStorage))
def checkStorage(self):
from pyanaconda.storage_utils import sanity_check, SanityError, SanityWarning
threadMgr.wait(constants.THREAD_EXECUTE_STORAGE)
hubQ.send_not_ready(self._mainSpokeClass)
hubQ.send_message(self._mainSpokeClass, _("Checking storage configuration..."))
exns = sanity_check(self.storage, min_ram=self._min_ram)
errors = [str(exn) for exn in exns if isinstance(exn, SanityError)]
warnings = [str(exn) for exn in exns if isinstance(exn, SanityWarning)]
(StorageChecker.errors, StorageChecker.warnings) = (errors, warnings)
hubQ.send_ready(self._mainSpokeClass, True)
for e in StorageChecker.errors:
self.log.error(e)
for w in StorageChecker.warnings:
self.log.warning(w)
class SourceSwitchHandler(object, metaclass=ABCMeta):
""" A class that can be used as a mixin handling
installation source switching.
It will correctly switch to the new method
and cleanup any previous method set.
"""
@abstractproperty
def data(self):
pass
@abstractproperty
def storage(self):
pass
def __init__(self):
self._device = None
self._current_iso_path = None
def unset_source(self):
"""Unset an already selected source method.
Unset the source in kickstart and notify the payload so that it can correctly
release all related resources (unmount iso files, drop caches, etc.).
"""
self._clean_hdd_iso()
self.data.method.method = None
payloadMgr.restartThread(self.storage, self.data, self.payload, self.instclass, checkmount=False) # pylint: disable=no-member
threadMgr.wait(constants.THREAD_PAYLOAD_RESTART)
threadMgr.wait(constants.THREAD_PAYLOAD)
def _clean_hdd_iso(self):
""" Clean HDD ISO usage
This means unmounting the partition and unprotecting it,
so it can be used for the installation.
"""
if self.data.method.method == "harddrive" and self.data.method.partition:
part = self.data.method.partition
dev = self.storage.devicetree.get_device_by_name(part)
if dev:
dev.protected = False
# the hdd iso cleanup function might be run multiple times,
# so make sure the partition still is in the list of protected devices
if part in self.storage.config.protectedDevSpecs:
self.storage.config.protectedDevSpecs.remove(part)
def set_source_hdd_iso(self, device, iso_path):
""" Switch to the HDD ISO install source
:param partition: name of the partition hosting the ISO
:type partition: string
:param iso_path: full path to the source ISO file
:type iso_path: string
"""
partition = device.name
# the GUI source spoke also does the copy
old_source = copy.copy(self.data.method)
# if a different partition was used previously, unprotect it
if old_source.method == "harddrive" and old_source.partition != partition:
self._clean_hdd_iso()
# protect current device
if device:
device.protected = True
self.storage.config.protectedDevSpecs.append(device.name)
self.data.method.method = "harddrive"
self.data.method.partition = partition
# the / gets stripped off by payload.ISOImage
self.data.method.dir = "/" + iso_path
# as we already made the device protected when
# switching to it, we don't need to protect it here
def set_source_url(self, url=None):
""" Switch to install source specified by URL """
# clean any old HDD ISO sources
self._clean_hdd_iso()
self.data.method.method = "url"
if url is not None:
self.data.method.url = url
def set_source_nfs(self, opts=None):
""" Switch to NFS install source """
# clean any old HDD ISO sources
self._clean_hdd_iso()
self.data.method.method = "nfs"
if opts is not None:
self.data.method.opts = opts
def set_source_cdrom(self):
""" Switch to cdrom install source """
# clean any old HDD ISO sources
self._clean_hdd_iso()
self.data.method.method = "cdrom"
def set_source_closest_mirror(self):
""" Switch to the closest mirror install source """
# clean any old HDD ISO sources
self._clean_hdd_iso()
self.data.method.method = None
class InputCheck(object):
"""Handle an input validation check.
This class is used by classes that implement InputCheckHandler to
manage and manipulate input validation check instances.
"""
# Use as a return value to indicate a passed check
CHECK_OK = None
# Treat the check as failed but don't display anything
# This can be used, for example, to reject empty input without setting
# a big loud error message.
CHECK_SILENT = ""
# Read-only properties
input_obj = property(lambda s: s._input_obj,
doc="The input to check.")
run_check = property(lambda s: s._run_check,
doc="A function to call to perform the input check.")
data = property(lambda s: s._data,
doc="Optional data associated with the input check.")
check_status = property(lambda s: s._check_status,
doc="The current status of the check")
def __init__(self, parent, input_obj, run_check, data=None):
"""Create a new input validation check.
:param InputCheckHandler parent: The InputCheckHandler object to which this
check is being added.
:param function input_obj: An object representing the input to check.
:param function run_check: A function to call to perform the input check. This
function is called with the InputCheck object as a
parameter. The return value an object representing
the error state, or CHECK_OK if the check succeeds.
:param data: Optional data associated with the input check
"""
self._parent = parent
self._input_obj = input_obj
self._run_check = run_check
self._data = data
self._check_status = None
self._enabled = True
def update_check_status(self):
"""Run an input validation check."""
if not self.enabled:
return
self._check_status = self._run_check(self)
self._parent.set_status(self)
@property
def enabled(self):
"""Whether the check is enabled or not.
Disabling a check indicates that the status will not change if
the input changes. The value of check_status will be the result of
the last time the InputCheck was run when enabled. Disabled checks
will not be included in InputCheckHandler.failed_checks.
"""
return self._enabled
@enabled.setter
def enabled(self, value):
self._enabled = value
class InputCheckHandler(object, metaclass=ABCMeta):
"""Provide a framework for adding input validation checks to a screen.
This helper class provides a mean of defining and associating input
validation checks with an input screen. Running the checks and acting
upon the results is left up to the subclasses. Classes implementing
InputCheckHandler should ensure that the checks are run at the
appropriate times (e.g., calling InputCheck.update_check_status when
input is changed), and that input for the screen is not accepted if
self.failed_checks is not empty.
See GUIInputCheckHandler and GUISpokeInputCheckHandler for additional
functionality.
"""
def __init__(self):
self._check_list = []
def _check_re(self, inputcheck):
"""Perform an input validation check against a regular expression."""
if inputcheck.data['regex'].match(self.get_input(inputcheck.input_obj)):
return inputcheck.CHECK_OK
else:
return inputcheck.data['message']
@abstractmethod
def get_input(self, input_obj):
"""Return the input string from an input object.
:param input_obj: The input object
:returns: An input string
:rtype: str
"""
pass
@abstractmethod
def set_status(self, inputcheck):
"""Update the status of the window from the input validation results.
This function could, for example, set or clear an error on the window,
or display a message near an input area with invalid data.
:param InputCheck inputcheck: The InputCheck object whose status last changed.
"""
pass
def add_check(self, input_obj, run_check, data=None):
"""Add an input validation check to this object.
:param input_obj: An object representing the input to check.
:param function run_check: A function to call to perform the input check. This
function is called with the InputCheck object as a
parameter. The return value an object representing
the error state, or CHECK_OK if the check succeeds.
:param data: Optional data associated with the input check
:returns: The InputCheck object created.
:rtype: InputCheck
"""
checkRef = InputCheck(self, input_obj, run_check, data)
self._check_list.append(checkRef)
return checkRef
def add_re_check(self, input_obj, regex, message):
"""Add a check using a regular expression.
:param function input_obj: An object representing the input to check.
:param re.RegexObject regex: The regular expression to check input against.
:param str message: A message to return for failed checks
:returns: The InputCheck object created.
:rtype: InputCheck
"""
return self.add_check(input_obj=input_obj, run_check=self._check_re,
data={'regex': regex, 'message': message})
def remove_check(self, inputcheck):
"""Remove an input check.
If the check being removed is not in the OK status, the status will
be set to CHECK_OK and set_status will be called.
:param inputcheck InputCheck: the InputCheck object to remove
:raise ValueError: if the inputcheck does not exist for this InputCheckHandler
"""
self._check_list.remove(inputcheck)
if inputcheck.check_status != InputCheck.CHECK_OK:
inputcheck._check_status = InputCheck.CHECK_OK
self.set_status(inputcheck)
@property
def failed_checks(self):
"""A generator of all failed input checks"""
return (c for c in self._check_list \
if c.enabled and c.check_status != InputCheck.CHECK_OK)
@property
def failed_checks_with_message(self):
"""A generator of all failed input checks with an error message"""
return (c for c in self._check_list \
if c.enabled and c.check_status not in (InputCheck.CHECK_OK, InputCheck.CHECK_SILENT))
@property
def checks(self):
"""An iterator over all input checks"""
return self._check_list.__iter__()