def refresh(self): super().refresh() threadMgr.wait(THREAD_PAYLOAD) # Create a new software selection cache. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) self._selection_cache.apply_selection_data(self._selection) # Refresh up the UI. self._refresh_environments() self._refresh_groups() # Set up the info bar. self.clear_info() if self._errors: self.set_warning(_( "Error checking software dependencies. " " <a href=\"\">Click for details.</a>" )) elif self._warnings: self.set_warning(_( "Warning checking software dependencies. " " <a href=\"\">Click for details.</a>" ))
def __init__(self, data, storage, payload): super().__init__(data, storage, payload) self.title = N_("Software selection") self._container = None self.errors = [] self._tx_id = None # Get the packages configuration. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) # Are we taking values (package list) from a kickstart file? self._kickstarted = flags.automatedInstall and self.payload.proxy.PackagesKickstarted
def setup(self, args): """Set up the spoke right before it is used.""" super().setup(args) # Wait for the payload to be ready. threadMgr.wait(THREAD_SOFTWARE_WATCHER) threadMgr.wait(THREAD_PAYLOAD) # Create a new software selection cache. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) self._selection_cache.apply_selection_data(self._selection) return True
def setUp(self): """Set up the test.""" self.environment_data = CompsEnvironmentData() self.environment_data.id = "e1" self.environment_data.optional_groups = ["g1", "g2", "g3", "g4", "g5"] self.dnf_manager = Mock(spec=DNFManager) self.dnf_manager.resolve_environment.return_value = True self.dnf_manager.get_environment_data.return_value = self.environment_data self.dnf_manager.get_group_data.side_effect = self._get_group_data self.dnf_manager.resolve_group.return_value = True self.cache = SoftwareSelectionCache(self.dnf_manager)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._errors = [] self._warnings = [] self._tx_id = None # Get the packages selection data. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) self._kickstarted = flags.automatedInstall and self.payload.proxy.PackagesKickstarted # Get the UI elements. self._environment_list_box = self.builder.get_object( "environmentListBox") self._addon_list_box = self.builder.get_object("addonListBox") # Connect viewport scrolling with listbox focus events environment_viewport = self.builder.get_object("environmentViewport") self._environment_list_box.set_focus_vadjustment( Gtk.Scrollable.get_vadjustment(environment_viewport)) addon_viewport = self.builder.get_object("addonViewport") self._addon_list_box.set_focus_vadjustment( Gtk.Scrollable.get_vadjustment(addon_viewport))
class SoftwareSelectionCacheTestCase(unittest.TestCase): """Test the cache for the Software Selection spoke.""" def setUp(self): """Set up the test.""" self.environment_data = CompsEnvironmentData() self.environment_data.id = "e1" self.environment_data.optional_groups = ["g1", "g2", "g3", "g4", "g5"] self.dnf_manager = Mock(spec=DNFManager) self.dnf_manager.resolve_environment.return_value = True self.dnf_manager.get_environment_data.return_value = self.environment_data self.dnf_manager.get_group_data.side_effect = self._get_group_data self.dnf_manager.resolve_group.return_value = True self.cache = SoftwareSelectionCache(self.dnf_manager) def _get_group_data(self, group): """Mock the get_group_data method of the DNF manager.""" data = CompsGroupData() data.id = group return data def test_available_environments(self): """Test the available_environments property.""" self.dnf_manager.environments = [] assert self.cache.available_environments == [] self.dnf_manager.environments = ["e1", "e2"] assert self.cache.available_environments == ["e1", "e2"] def test_is_environment_selected(self): """Test the is_environment_selected method.""" assert self.cache.is_environment_selected("e1") is False self.cache.select_environment("e1") assert self.cache.is_environment_selected("e1") is True self.cache.select_environment("") assert self.cache.is_environment_selected("e1") is False def test_environment(self): """Test the environment property.""" assert self.cache.environment == "" self.cache.select_environment("e1") assert self.cache.environment == "e1" def test_available_groups(self): """Test the available_groups property.""" self.cache.select_environment("e1") assert self.cache.available_groups == ["g1", "g2", "g3", "g4", "g5"] self.cache.select_environment("") assert self.cache.available_groups == [] def test_groups(self): """Test the groups property.""" self.environment_data.default_groups = ["g2", "g4"] self.cache.select_environment("e1") assert self.cache.groups == ["g2", "g4"] self.cache.select_group("g1") assert self.cache.groups == ["g1", "g2", "g4"] self.cache.deselect_group("g4") assert self.cache.groups == ["g1", "g2"] def test_is_group_selected(self): """Test the is_group_selected method.""" self.environment_data.default_groups = ["g2", "g4"] self.cache.select_environment("e1") assert self.cache.is_group_selected("g1") is False assert self.cache.is_group_selected("g4") is True assert self.cache.is_group_selected("g7") is False self.cache.select_group("g1") assert self.cache.is_group_selected("g1") is True assert self.cache.is_group_selected("g4") is True assert self.cache.is_group_selected("g7") is False self.cache.deselect_group("g4") assert self.cache.is_group_selected("g1") is True assert self.cache.is_group_selected("g4") is False assert self.cache.is_group_selected("g7") is False def test_apply_selection_data(self): """Test the apply_selection_data method.""" selection = PackagesSelectionData() selection.environment = "e1" selection.groups = ["g1", "g2", "g3"] self.cache.apply_selection_data(selection) assert self.cache.environment == "e1" assert self.cache.groups == ["g1", "g2", "g3"] def test_apply_selection_data_default_environment(self): """Test the apply_selection_data method with a default environment.""" self.dnf_manager.default_environment = "e1" self.dnf_manager.resolve_environment.return_value = False selection = PackagesSelectionData() selection.environment = "e2" self.cache.apply_selection_data(selection) assert self.cache.environment == "e1" assert self.cache.groups == [] def test_apply_selection_data_invalid_environment(self): """Test the apply_selection_data method with an invalid environment.""" self.dnf_manager.default_environment = "" self.dnf_manager.resolve_environment.return_value = False selection = PackagesSelectionData() selection.environment = "e2" self.cache.apply_selection_data(selection) assert self.cache.environment == "" assert self.cache.groups == [] def test_apply_selection_data_invalid_groups(self): """Test the apply_selection_data method with invalid groups.""" self.dnf_manager.resolve_group.return_value = False selection = PackagesSelectionData() selection.environment = "e1" selection.groups = ["g1", "g2", "g3"] self.cache.apply_selection_data(selection) assert self.cache.environment == "e1" assert self.cache.groups == [] def test_get_selection_data(self): """Test the get_selection_data method.""" self.cache.select_environment("e1") self.cache.select_group("g1") self.cache.select_group("g2") self.cache.select_group("g3") expected = PackagesSelectionData() expected.environment = "e1" expected.groups = ["g1", "g2", "g3"] data = self.cache.get_selection_data() assert compare_data(data, expected) def test_default_selection(self): """Test the default environment and group selection.""" self.environment_data.id = "e1" self.environment_data.default_groups = ["g2", "g4"] self.cache.select_environment("e1") assert self.cache.groups == ["g2", "g4"] self.cache.select_group("g2") assert self.cache.groups == ["g2", "g4"] self.cache.select_group("g4") assert self.cache.groups == ["g2", "g4"] self.environment_data.id = "e2" self.environment_data.default_groups = ["g1", "g3", "g5"] self.cache.select_environment("e2") assert self.cache.groups == ["g1", "g3", "g5"] def test_selection(self): """Test the environment and group selection.""" self.environment_data.id = "e1" self.cache.select_environment("e1") assert self.cache.groups == [] self.cache.select_group("g2") assert self.cache.groups == ["g2"] self.cache.select_group("g4") assert self.cache.groups == ["g2", "g4"] self.environment_data.id = "e2" self.environment_data.default_groups = ["g1", "g3", "g5"] self.cache.select_environment("e2") assert self.cache.groups == ["g1", "g2", "g3", "g4", "g5"] def test_deselection(self): """Test the environment and group deselection.""" self.environment_data.id = "e1" self.environment_data.default_groups = ["g2", "g4"] self.cache.select_environment("e1") assert self.cache.groups == ["g2", "g4"] self.cache.select_group("g1") assert self.cache.groups == ["g1", "g2", "g4"] self.cache.deselect_group("g4") assert self.cache.groups == ["g1", "g2"] self.cache.select_group("g5") assert self.cache.groups == ["g1", "g2", "g5"] self.cache.deselect_group("g5") assert self.cache.groups == ["g1", "g2"] self.environment_data.id = "e2" self.environment_data.default_groups = ["g2", "g3", "g4", "g5"] self.cache.select_environment("e2") assert self.cache.groups == ["g1", "g2", "g3"]
class SoftwareSelectionSpoke(NormalSpoke): """ .. inheritance-diagram:: SoftwareSelectionSpoke :parts: 3 """ builderObjects = ["addonStore", "environmentStore", "softwareWindow"] mainWidgetName = "softwareWindow" uiFile = "spokes/software_selection.glade" helpFile = "SoftwareSpoke.xml" category = SoftwareCategory icon = "package-x-generic-symbolic" title = CN_("GUI|Spoke", "_Software Selection") @classmethod def should_run(cls, environment, data): """Don't run for any non-package payload.""" if not NormalSpoke.should_run(environment, data): return False return context.payload_type == PAYLOAD_TYPE_DNF def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._errors = [] self._warnings = [] self._tx_id = None # Get the packages selection data. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) self._kickstarted = flags.automatedInstall and self.payload.proxy.PackagesKickstarted # Get the UI elements. self._environment_list_box = self.builder.get_object("environmentListBox") self._addon_list_box = self.builder.get_object("addonListBox") # Connect viewport scrolling with listbox focus events environment_viewport = self.builder.get_object("environmentViewport") self._environment_list_box.set_focus_vadjustment( Gtk.Scrollable.get_vadjustment(environment_viewport) ) addon_viewport = self.builder.get_object("addonViewport") self._addon_list_box.set_focus_vadjustment( Gtk.Scrollable.get_vadjustment(addon_viewport) ) @property def _dnf_manager(self): """The DNF manager.""" return self.payload.dnf_manager @property def _selection(self): """The packages selection.""" return self.payload.get_packages_selection() def initialize(self): """Initialize the spoke.""" super().initialize() self.initialize_start() threadMgr.add(AnacondaThread( name=THREAD_SOFTWARE_WATCHER, target=self._initialize )) def _initialize(self): """Initialize the spoke in a separate thread.""" threadMgr.wait(THREAD_PAYLOAD) # Initialize and check the software selection. self._initialize_selection() # Update the status. hubQ.send_ready(self.__class__.__name__) # Report that the software spoke has been initialized. self.initialize_done() def _initialize_selection(self): """Initialize and check the software selection.""" if not self.payload.base_repo: log.debug("Skip the initialization of the software selection.") return if not self._kickstarted: # Use the default environment. self._selection_cache.select_environment( self._dnf_manager.default_environment ) # Apply the default selection. self.apply() # Check the initial software selection. self.execute() # Wait for the software selection thread that might be started by execute(). # We are already running in a thread, so it should not needlessly block anything # and only like this we can be sure we are really initialized. threadMgr.wait(THREAD_CHECK_SOFTWARE) @property def ready(self): """Is the spoke ready? By default, the software selection spoke is not ready. We have to wait until the installation source spoke is completed. This could be because the user filled something out, or because we're done fetching repo metadata from the mirror list, or we detected a DVD/CD. """ return not self._processing_data and self._source_is_set @property def _source_is_set(self): """Is the installation source set?""" return self.payload.base_repo is not None @property def _source_has_changed(self): """Has the installation source changed?""" return self._tx_id != self.payload.tx_id @property def _processing_data(self): """Is the spoke processing data?""" return threadMgr.get(THREAD_SOFTWARE_WATCHER) \ or threadMgr.get(THREAD_PAYLOAD) \ or threadMgr.get(THREAD_CHECK_SOFTWARE) @property def status(self): """The status of the spoke.""" if self._processing_data: return _("Processing...") if is_cdn_registration_required(self.payload): return _("Red Hat CDN requires registration.") if not self._source_is_set: return _("Installation source not set up") if self._source_has_changed: return _("Source changed - please verify") if self._errors: return _("Error checking software selection") if self._warnings: return _("Warning checking software selection") return get_software_selection_status( dnf_manager=self._dnf_manager, selection=self._selection, kickstarted=self._kickstarted ) @property def completed(self): """Is the spoke complete?""" return self.ready \ and not self._errors \ and not self._source_has_changed \ and is_software_selection_complete( dnf_manager=self._dnf_manager, selection=self._selection, kickstarted=self._kickstarted ) def refresh(self): super().refresh() threadMgr.wait(THREAD_PAYLOAD) # Create a new software selection cache. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) self._selection_cache.apply_selection_data(self._selection) # Refresh up the UI. self._refresh_environments() self._refresh_groups() # Set up the info bar. self.clear_info() if self._errors: self.set_warning(_( "Error checking software dependencies. " " <a href=\"\">Click for details.</a>" )) elif self._warnings: self.set_warning(_( "Warning checking software dependencies. " " <a href=\"\">Click for details.</a>" )) def _refresh_environments(self): """Create rows for all available environments.""" self._clear_listbox(self._environment_list_box) for environment in self._selection_cache.available_environments: # Get the environment data. data = self._dnf_manager.get_environment_data(environment) selected = self._selection_cache.is_environment_selected(environment) # Add a new environment row. row = EnvironmentListBoxRow(data, selected) self._environment_list_box.insert(row, -1) self._environment_list_box.show_all() def _refresh_groups(self): """Create rows for all available groups.""" self._clear_listbox(self._addon_list_box) if self._selection_cache.environment: # Get the environment data. environment_data = self._dnf_manager.get_environment_data( self._selection_cache.environment ) # Add all optional groups. for group in environment_data.optional_groups: self._add_group_row(group) # Add the separator. if environment_data.optional_groups and environment_data.visible_groups: self._addon_list_box.insert(SeparatorRow(), -1) # Add user visible groups that are not optional. for group in environment_data.visible_groups: if group in environment_data.optional_groups: continue self._add_group_row(group) self._addon_list_box.show_all() def _add_group_row(self, group): """Add a new row for the specified group.""" # Get the group data. data = self._dnf_manager.get_group_data(group) selected = self._selection_cache.is_group_selected(group) # Add a new group row. row = GroupListBoxRow(data, selected) self._addon_list_box.insert(row, -1) def _clear_listbox(self, listbox): for child in listbox.get_children(): listbox.remove(child) del child def apply(self): """Apply the changes.""" self._kickstarted = False selection = self._selection_cache.get_selection_data() log.debug("Setting new software selection: %s", selection) self.payload.set_packages_selection(selection) hubQ.send_not_ready(self.__class__.__name__) hubQ.send_not_ready("SourceSpoke") def execute(self): """Execute the changes.""" threadMgr.add(AnacondaThread( name=THREAD_CHECK_SOFTWARE, target=self._check_software_selection )) def _check_software_selection(self): hubQ.send_message(self.__class__.__name__, _("Checking software dependencies...")) self.payload.bump_tx_id() # Run the validation task. from pyanaconda.modules.payloads.payload.dnf.validation import CheckPackagesSelectionTask task = CheckPackagesSelectionTask( dnf_manager=self._dnf_manager, selection=self._selection, ) # Get the validation report. report = task.run() log.debug("The selection has been checked: %s", report) self._errors = list(report.error_messages) self._warnings = list(report.warning_messages) self._tx_id = self.payload.tx_id hubQ.send_ready(self.__class__.__name__) hubQ.send_ready("SourceSpoke") # Signal handlers def on_environment_activated(self, listbox, row): if not isinstance(row, EnvironmentListBoxRow): return # Mark the environment as selected. environment = row.get_environment_id() self._selection_cache.select_environment(environment) # Update the row button. row.toggle_button(True) # Update the screen. self._refresh_groups() def on_addon_activated(self, listbox, row): # Skip the separator. if not isinstance(row, GroupListBoxRow): return # Mark the group as selected or deselected. group = row.get_group_id() selected = not self._selection_cache.is_group_selected(group) if selected: self._selection_cache.select_group(row.data.id) else: self._selection_cache.deselect_group(row.data.id) # Update the row button. row.toggle_button(selected) def on_info_bar_clicked(self, *args): if self._errors: self._show_error_dialog() elif self._warnings: self._show_warning_dialog() def _show_error_dialog(self): label = _( "The software marked for installation has the following errors. " "This is likely caused by an error with your installation source. " "You can quit the installer, change your software source, or change " "your software selections." ) buttons = [ C_("GUI|Software Selection|Error Dialog", "_Quit"), C_("GUI|Software Selection|Error Dialog", "_Modify Software Source"), C_("GUI|Software Selection|Error Dialog", "Modify _Selections") ] dialog = DetailedErrorDialog(self.data, buttons=buttons, label=label) with self.main_window.enlightbox(dialog.window): errors = "\n".join(self._errors) dialog.refresh(errors) rc = dialog.run() dialog.window.destroy() if rc == 0: # Quit the installation. ipmi_abort(scripts=self.data.scripts) sys.exit(0) elif rc == 1: # Send the user to the installation source spoke. self.skipTo = "SourceSpoke" self.window.emit("button-clicked") def _show_warning_dialog(self): label = _( "The software marked for installation has the following warnings. " "These are not fatal, but you may wish to make changes to your " "software selections." ) buttons = [ C_("GUI|Software Selection|Warning Dialog", "_OK") ] dialog = DetailedErrorDialog(self.data, buttons=buttons, label=label) with self.main_window.enlightbox(dialog.window): warnings = "\n".join(self._warnings) dialog.refresh(warnings) dialog.run() dialog.window.destroy()
class SoftwareSpoke(NormalTUISpoke): """The spoke for choosing the software. .. inheritance-diagram:: SoftwareSpoke :parts: 3 """ helpFile = "SoftwareSpoke.txt" category = SoftwareCategory @staticmethod def get_screen_id(): """Return a unique id of this UI screen.""" return "software-selection" @classmethod def should_run(cls, environment, data): """Don't run for any non-package payload.""" if not NormalTUISpoke.should_run(environment, data): return False return context.payload_type == PAYLOAD_TYPE_DNF def __init__(self, data, storage, payload): super().__init__(data, storage, payload) self.title = N_("Software selection") self._container = None self._tx_id = None self._errors = [] self._warnings = [] # Get the packages configuration. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) # Are we taking values (package list) from a kickstart file? self._kickstarted = flags.automatedInstall and self.payload.proxy.PackagesKickstarted @property def _dnf_manager(self): """The DNF manager.""" return self.payload.dnf_manager @property def _selection(self): """The packages selection.""" return self.payload.get_packages_selection() def initialize(self): """Initialize the spoke.""" super().initialize() self.initialize_start() threadMgr.add( AnacondaThread(name=THREAD_SOFTWARE_WATCHER, target=self._initialize)) def _initialize(self): """Initialize the spoke in a separate thread.""" threadMgr.wait(THREAD_PAYLOAD) # Initialize and check the software selection. self._initialize_selection() # Report that the software spoke has been initialized. self.initialize_done() def _initialize_selection(self): """Initialize and check the software selection.""" if not self._source_is_set: log.debug("Skip the initialization of the software selection.") return if not self._kickstarted: # Use the default environment. self._selection_cache.select_environment( self._dnf_manager.default_environment) # Apply the default selection. self.apply() # Check the initial software selection. self.execute() # Wait for the software selection thread that might be started by execute(). # We are already running in a thread, so it should not needlessly block anything # and only like this we can be sure we are really initialized. threadMgr.wait(THREAD_CHECK_SOFTWARE) @property def ready(self): """Is the spoke ready? By default, the software selection spoke is not ready. We have to wait until the installation source spoke is completed. This could be because the user filled something out, or because we're done fetching repo metadata from the mirror list, or we detected a DVD/CD. """ return not self._processing_data and self._source_is_set @property def _source_is_set(self): """Is the installation source set?""" return self.payload.base_repo is not None @property def _source_has_changed(self): """Has the installation source changed?""" return self._tx_id != self.payload.tx_id @property def _processing_data(self): """Is the spoke processing data?""" return threadMgr.get(THREAD_SOFTWARE_WATCHER) \ or threadMgr.get(THREAD_PAYLOAD) \ or threadMgr.get(THREAD_CHECK_SOFTWARE) @property def status(self): """The status of the spoke.""" if self._processing_data: return _("Processing...") if not self._source_is_set: return _("Installation source not set up") if self._source_has_changed: return _("Source changed - please verify") if self._errors: return _("Error checking software selection") if self._warnings: return _("Warning checking software selection") return get_software_selection_status(dnf_manager=self._dnf_manager, selection=self._selection, kickstarted=self._kickstarted) @property def completed(self): """Is the spoke complete?""" return self.ready \ and not self._errors \ and not self._source_has_changed \ and is_software_selection_complete( dnf_manager=self._dnf_manager, selection=self._selection, kickstarted=self._kickstarted ) def setup(self, args): """Set up the spoke right before it is used.""" super().setup(args) # Wait for the payload to be ready. threadMgr.wait(THREAD_SOFTWARE_WATCHER) threadMgr.wait(THREAD_PAYLOAD) # Create a new software selection cache. self._selection_cache = SoftwareSelectionCache(self._dnf_manager) self._selection_cache.apply_selection_data(self._selection) return True def refresh(self, args=None): """ Refresh screen. """ NormalTUISpoke.refresh(self, args) self._container = None if not self._source_is_set: message = TextWidget( _("Installation source needs to be set up first.")) self.window.add_with_separator(message) return threadMgr.wait(THREAD_CHECK_SOFTWARE) self._container = ListColumnContainer(columns=2, columns_width=38, spacing=2) for environment in self._selection_cache.available_environments: data = self._dnf_manager.get_environment_data(environment) selected = self._selection_cache.is_environment_selected( environment) widget = CheckboxWidget(title=data.name, completed=selected) self._container.add(widget, callback=self._select_environment, data=data.id) self.window.add_with_separator(TextWidget(_("Base environment"))) self.window.add_with_separator(self._container) if self._errors or self._warnings: messages = "\n".join(self._errors or self._warnings) self.window.add_with_separator(TextWidget(messages)) def _select_environment(self, data): self._selection_cache.select_environment(data) def input(self, args, key): """Handle the user input.""" if self._container is None: return super().input(args, key) if self._container.process_user_input(key): return InputState.PROCESSED_AND_REDRAW if key.lower() == Prompt.CONTINUE: if self._selection_cache.environment: # The environment was selected, switch the screen. spoke = AdditionalSoftwareSpoke(self.data, self.storage, self.payload, self._selection_cache) ScreenHandler.push_screen_modal(spoke) self.apply() self.execute() return InputState.PROCESSED_AND_CLOSE return super().input(args, key) def apply(self): """Apply the changes.""" self._kickstarted = False selection = self._selection_cache.get_selection_data() log.debug("Setting new software selection: %s", selection) self.payload.set_packages_selection(selection) def execute(self): """Execute the changes.""" threadMgr.add( AnacondaThread(name=THREAD_CHECK_SOFTWARE, target=self._check_software_selection)) def _check_software_selection(self): """Check the software selection.""" self.payload.bump_tx_id() # Run the validation task. from pyanaconda.modules.payloads.payload.dnf.validation import CheckPackagesSelectionTask task = CheckPackagesSelectionTask( dnf_manager=self._dnf_manager, selection=self._selection, ) # Get the validation report. report = task.run() self._errors = list(report.error_messages) self._warnings = list(report.warning_messages) self._tx_id = self.payload.tx_id print("\n".join(report.get_messages())) def closed(self): """The spoke has been closed.""" super().closed() # Run the setup method again on entry. self.screen_ready = False