anaconda/anaconda-40.22.3.13/pyanaconda/installation_tasks.py

307 lines
9.4 KiB
Python
Raw Normal View History

2024-11-14 21:39:56 -08:00
# installation_tasks.py
# Container classes for running of installation tasks.
#
# Copyright (C) 2016 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.
#
import time
from dasbus.error import DBusError
from pyanaconda.core.signal import Signal
from pyanaconda.errors import errorHandler, ERROR_RAISE
from pyanaconda.modules.common.task import sync_run_task
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
class BaseTask(object):
"""A base class for Task and TaskQueue.
It holds shared methods, properties and signals.
"""
def __init__(self, name):
self._name = name
self._parent = None
self._elapsed_time = None
self.started = Signal()
self.completed = Signal()
@property
def name(self):
"""Task name.
:returns: task name
:rtype: str
"""
return self._name
@property
def elapsed_time(self):
"""Elapsed time since the task has been started in milliseconds."""
return self._elapsed_time
@property
def summary(self):
"""A description of the task - to be overridden by subclasses."""
raise NotImplementedError
def set_parent(self, queue):
"""Set the parent task queue."""
self._parent = queue
def start(self):
"""Start the task."""
# trigger the "started" signal
self.started.emit(self)
# run the task
start_timestamp = time.time()
self._run()
done_timestamp = time.time()
self._elapsed_time = done_timestamp - start_timestamp
# trigger the "completed" signal
self.completed.emit(self)
def _run(self):
"""Run the task - to be overridden by sub-classes."""
raise NotImplementedError
class TaskQueue(BaseTask):
"""TaskQueue represents a queue of TaskQueues or Tasks.
TaskQueues and Tasks can be mixed in a single TaskQueue.
"""
def __init__(self, name, status_message=None, task_category=None):
super().__init__(name)
self._task_category = task_category
self._status_message = status_message
# the list backing this TaskQueue instance
self._queue = []
# triggered if a TaskQueue contained in this one was started/completed
self.queue_started = Signal()
self.queue_completed = Signal()
# triggered when a task is started
self.task_started = Signal()
self.task_completed = Signal()
@property
def task_category(self):
"""A category describing the Queue is trying to achieve.
Eq. "Converting all foo into bar."
The current main usecase is to set the ProgressHub status message when
a TaskQueue is started.
:returns: a task category
:rtype: str
"""
return self._task_category
@property
def status_message(self):
"""A status message describing the Queue is trying to achieve.
Eq. "Converting all foo into bar."
The current main usecase is to set the ProgressHub status message when
a TaskQueue is started.
:returns: a status message
:rtype: str
"""
return self._status_message
@property
def items(self):
"""Task and queues contained in this queue.
:return: a list of tasks and queues
"""
return self._queue
@property
def nested_items(self):
"""Tasks and queues contained in this and all nested task queues.
:return: a list of tasks and queues
"""
items = []
for item in self._queue:
items.append(item)
if isinstance(item, TaskQueue):
items.extend(item.nested_items)
return items
@property
def queue_count(self):
"""Returns number of TaskQueues contained in this and all nested TaskQueues.
:returns: number of queues
:rtype: int
"""
return len([i for i in self.nested_items if isinstance(i, TaskQueue)])
@property
def task_count(self):
"""Returns number of tasks contained in this and all nested TaskQueues.
:returns: number of tasks
:rtype: int
"""
return len([i for i in self.nested_items if not isinstance(i, TaskQueue)])
@property
def summary(self):
"""Return a multi-line summary of the contents of the task queue.
:returns: summary of task queue contents
:rtype: str
"""
if self._parent is None:
message = "Top-level task queue: %s\n" % self.name
# this is the top-level queue, so add some "global" stats
message += "Number of task queues: %d\n" % self.queue_count
message += "Number of tasks: %d\n" % self.task_count
message += "Task & task group listing:\n"
else:
message = "Task queue: %s\n" % self.name
for item in self._queue:
for line in item.summary.splitlines():
message += " %s\n" % line
return message.strip()
def _run(self):
"""Run the task queue."""
for item in self._queue:
# start the item (TaskQueue/Task)
item.start()
# implement the Python list "interface" and make sure parent is always
# set to a correct value
def append(self, item):
item.started.connect(self.task_started.emit)
item.completed.connect(self.task_completed.emit)
if isinstance(item, TaskQueue):
# connect own start/completion signal to parents queue start/completion signal
item.started.connect(self.queue_started.emit)
item.completed.connect(self.queue_completed.emit)
# propagate start/completion signals from nested queues/tasks
item.queue_started.connect(self.queue_started.emit)
item.queue_completed.connect(self.queue_completed.emit)
self._queue.append(item)
item.set_parent(self)
def append_dbus_tasks(self, service_id, dbus_tasks):
"""Append DBus Tasks from a module to the TaskQueue.
:param service_id: DBusServiceIdentifier instance corresponding to an Anaconda DBus module
:param dbus_tasks: list of DBus Tasks paths
"""
for dbus_task_path in dbus_tasks:
task_proxy = service_id.get_proxy(dbus_task_path)
self.append(DBusTask(task_proxy))
class Task(BaseTask):
"""Task is a wrapper for a single installation related task.
It has a name and a callable (called task), which is the actual task to execute.
Arguments and keywoard arguments for the callable can be suplied by using
the task_args and task_kwargs options.
"""
def __init__(self, task_name, task_cb, task_args=None, task_kwargs=None):
super().__init__(task_name)
self._task_cb = task_cb
self._task_args = task_args or []
self._task_kwargs = task_kwargs or dict()
@property
def summary(self):
"""A description of the task.
:returns: a single line describing the task
:rtype: str
"""
return "Task: %s" % self.name
def _run(self):
"""Runs the task (callable) assigned to this Task class instance."""
try:
# Run the task.
self._task_cb(*self._task_args, **self._task_kwargs)
except Exception as e: # pylint: disable=broad-except
# Handle an error.
if errorHandler.cb(e) == ERROR_RAISE:
raise
class DBusTask(BaseTask):
"""Wrapper for a DBus installation task."""
def __init__(self, task_proxy):
"""Create a new task.
:param task_proxy: a DBus proxy of the task
"""
super().__init__(task_proxy.Name)
self._task_proxy = task_proxy
@property
def summary(self):
"""A description of the task.
:returns: a single line describing the task
:rtype: str
"""
return "Task: %s" % self.name
def _run(self):
"""Run the DBus task."""
try:
# Report the progress messages.
self._task_proxy.ProgressChanged.connect(self._progress_cb)
# Run the task.
sync_run_task(self._task_proxy)
except DBusError as e:
# Handle a remote error.
if errorHandler.cb(e) == ERROR_RAISE:
raise
finally:
# Disconnect from the signal.
self._task_proxy.ProgressChanged.disconnect()
def _progress_cb(self, step, message):
"""Callback for task progress reporting."""
log.info(message)