Patches 1/3 and 2/3 fix things that are needed for the EULA spoke to be collected and shown by the hub. PATCH 2/3 requires the changes made in the related anaconda patches. PATCH 3/3 adds the EULA spoke. The screenshots of the spoke can be found at:
http://vpodzime.fedorapeople.org/eula_i-s/
I don't know into which category the spoke should be put, but I don't want to add another category just for this one. Any suggestions? Shall we merge it into one of the existing categories and rename it? Or can EULA agreement be considered part of User Settings?
Vratislav Podzimek (3): Fix the base mask of initial_setup gui submodules Specify and use environment of the main hub EULA agreement spoke (#1000409)
initial_setup/gui/gui.py | 2 +- initial_setup/gui/hubs/initial_setup_hub.py | 7 +- initial_setup/gui/spokes/eula.glade | 113 ++++++++++++++++++++++++++++ initial_setup/gui/spokes/eula.py | 74 ++++++++++++++++++ initial_setup/product.py | 56 ++++++++++++++ 5 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 initial_setup/gui/spokes/eula.glade create mode 100644 initial_setup/gui/spokes/eula.py
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- initial_setup/gui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/initial_setup/gui/gui.py b/initial_setup/gui/gui.py index 3afe5da..0935f3b 100644 --- a/initial_setup/gui/gui.py +++ b/initial_setup/gui/gui.py @@ -34,7 +34,7 @@ class InitialSetupGraphicalUserInterface(GraphicalUserInterface): def _list_hubs(self): return [InitialSetupMainHub]
- basemask = "firstboot.gui" + basemask = "initial_setup.gui" basepath = os.path.dirname(__file__) paths = GraphicalUserInterface.paths + { "spokes": [(basemask + ".spokes.%s", os.path.join(basepath, "spokes"))],
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- initial_setup/gui/hubs/initial_setup_hub.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/initial_setup/gui/hubs/initial_setup_hub.py b/initial_setup/gui/hubs/initial_setup_hub.py index d0ac15b..ac13adc 100644 --- a/initial_setup/gui/hubs/initial_setup_hub.py +++ b/initial_setup/gui/hubs/initial_setup_hub.py @@ -1,3 +1,4 @@ +from pyanaconda.constants import FIRSTBOOT_ENVIRON from pyanaconda.ui.gui.hubs import Hub from pyanaconda.ui.gui.spokes import Spoke from pyanaconda.ui.common import collect @@ -30,6 +31,10 @@ class InitialSetupMainHub(Hub): builderObjects = ["summaryWindow"] mainWidgetName = "summaryWindow"
+ def __init__(self, *args): + Hub.__init__(self, *args) + self._environs = [FIRSTBOOT_ENVIRON] + def _collectCategoriesAndSpokes(self): """collects categories and spokes to be displayed on this Hub
@@ -43,7 +48,7 @@ class InitialSetupMainHub(Hub): # spokes belonging to all those categories. candidate_spokes = collect_spokes(self.paths["spokes"]) spokes = [spoke for spoke in candidate_spokes \ - if spoke.should_run("firstboot", self.data)] + if spoke.should_run(FIRSTBOOT_ENVIRON, self.data)]
for spoke in spokes: ret.setdefault(spoke.category, [])
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- initial_setup/gui/spokes/eula.glade | 113 ++++++++++++++++++++++++++++++++++++ initial_setup/gui/spokes/eula.py | 74 +++++++++++++++++++++++ initial_setup/product.py | 56 ++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 initial_setup/gui/spokes/eula.glade create mode 100644 initial_setup/gui/spokes/eula.py
diff --git a/initial_setup/gui/spokes/eula.glade b/initial_setup/gui/spokes/eula.glade new file mode 100644 index 0000000..ca71224 --- /dev/null +++ b/initial_setup/gui/spokes/eula.glade @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.6 --> + <!-- interface-requires AnacondaWidgets 1.0 --> + <object class="GtkTextBuffer" id="eulaBuffer"> + <property name="text" translatable="yes">The license will go here</property> + </object> + <object class="AnacondaSpokeWindow" id="eulaWindow"> + <property name="startup_id">filler</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="startup_id">filler</property> + <property name="window_name" translatable="yes">LICENSE INFORMATION</property> + <signal name="button-clicked" handler="on_back_clicked" swapped="no"/> + <child internal-child="main_box"> + <object class="GtkBox" id="AnacondaSpokeWindow-main_box1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child internal-child="nav_box"> + <object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1"> + <property name="app_paintable">True</property> + <property name="can_focus">False</property> + <child internal-child="nav_area"> + <object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1"> + <property name="can_focus">False</property> + <property name="margin_left">6</property> + <property name="margin_right">6</property> + <property name="margin_top">6</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child internal-child="alignment"> + <object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1"> + <property name="can_focus">False</property> + <property name="xscale">0.80000001192092896</property> + <property name="yscale">0.80000001192092896</property> + <child internal-child="action_area"> + <object class="GtkBox" id="mainBox"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="eulaBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkTextView" id="eulaView"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="vscroll_policy">natural</property> + <property name="editable">False</property> + <property name="cursor_visible">False</property> + <property name="buffer">eulaBuffer</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="agreeCheckButton"> + <property name="label" translatable="yes">I _agree to the License Agreement</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="on_check_button_toggled" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + </object> +</interface> diff --git a/initial_setup/gui/spokes/eula.py b/initial_setup/gui/spokes/eula.py new file mode 100644 index 0000000..5761fff --- /dev/null +++ b/initial_setup/gui/spokes/eula.py @@ -0,0 +1,74 @@ +"""EULA spoke for the Initial Setup""" + +from gi.repository import Pango +from pyanaconda.ui.common import FirstbootOnlySpokeMixIn +from pyanaconda.ui.gui.spokes import NormalSpoke +from pyanaconda.ui.gui.categories.localization import LocalizationCategory +from pyanaconda.constants import FIRSTBOOT_ENVIRON + +from initial_setup.product import get_license_lines, NoLicenseError + +# TODO: make translations work +_ = lambda x: x +N_ = lambda x: x + +__all__ = ["EULAspoke"] + +class EULAspoke(FirstbootOnlySpokeMixIn, NormalSpoke): + """The EULA spoke""" + + builderObjects = ["eulaBuffer", "eulaWindow"] + mainWidgetName = "eulaWindow" + uiFile = "eula.glade" + + icon = "application-certificate-symbolic" + title = N_("_LICENSE INFORMATION") + category = LocalizationCategory + + def __init__(self, *args): + NormalSpoke.__init__(self, *args) + + # TODO: any kickstart support? + self._agreed = False + + def initialize(self): + NormalSpoke.initialize(self) + + self._have_eula = True + self._eula_buffer = self.builder.get_object("eulaBuffer") + self._agree_check_button = self.builder.get_object("agreeCheckButton") + self._agree_label = self._agree_check_button.get_child() + self._agree_text = self._agree_label.get_text() + + itr = self._eula_buffer.get_iter_at_offset(0) + try: + for line in get_license_lines(): + self._eula_buffer.insert(itr, line) + except NoLicenseError: + self._have_eula = False + self._eula_buffer.set_text(_("No license found. Please report this " + "at http://bugzilla.redhat.com")) + + def refresh(self): + self._agree_check_button.set_sensitive(self._have_eula) + self._agree_check_button.set_active(self._agreed) + + def apply(self): + self._agreed = self._agree_check_button.get_active() + + @property + def completed(self): + return not self._have_eula or self._agreed + + @property + def status(self): + if not self._have_eula: + return _("No license found") + + return _("License agreed") if self._agreed else _("License not agreed") + + def on_check_button_toggled(self, checkbutton, *args): + if self._agree_check_button.get_active(): + self._agree_label.set_markup("<b>%s</b>" % self._agree_text) + else: + self._agree_label.set_markup(self._agree_text) diff --git a/initial_setup/product.py b/initial_setup/product.py index 40cf45d..ea5109f 100644 --- a/initial_setup/product.py +++ b/initial_setup/product.py @@ -1,6 +1,17 @@ """Module providing information about the installed product."""
+from pyanaconda.localization import find_best_locale_match +from pyanaconda.constants import DEFAULT_LANG +import os +import glob + RELEASE_STRING_FILE = "/etc/system-release" +LICENSE_FILE_GLOB = "/usr/share/doc/redhat-release*/EULA*" + +class NoLicenseError(Exception): + """Exception class for the "no EULA found" case""" + + pass
def product_title(): """ @@ -29,3 +40,48 @@ def is_final():
# doesn't really matter for the Initial Setup return True + +def get_license_lines(): + """ + Get lines of the license file best matching current localization settings. + + :return: generator generating lines of the license file + :rtype: generator + :raise NoLicenseError: if no license file was found + + """ + + all_eulas = glob.glob(LICENSE_FILE_GLOB) + non_localized_eulas = [] + langs = set() + for eula in all_eulas: + if "EULA_" in eula: + # license file for a specific locale + lang = eula.rsplit("EULA_", 1)[1] + if lang: + langs.add(lang) + else: + non_localized_eulas.append(eula) + + best_lang = find_best_locale_match(os.environ["LANG"], langs) + if not best_lang: + # nothing found for the current language, try the default one + best_lang = find_best_locale_match(DEFAULT_LANG, langs) + + if not best_lang: + # nothing found even for the default language, use non-localized or None + if non_localized_eulas: + best_eula = non_localized_eulas[0] + else: + raise NoLicenseError("No license file found for: %s" % LICENSE_FILE_GLOB) + else: + # use first of the best-matching EULA files (there should be only one) + best_eula = glob.glob(LICENSE_FILE_GLOB + ("_%s" % best_lang))[0] + + try: + with open(best_eula, "r") as fobj: + for line in fobj: + yield line + except IOError as ioerr: + raise NoLicenseError("Cannot read the license file: %s (%s)" % (best_eula, + ioerr))
This allows non-anaconda hubs to collect spoke for a specific environment without overriding the _createBox method that can be made more generic this way.
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/ui/common.py | 6 +++++- pyanaconda/ui/gui/hubs/__init__.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/pyanaconda/ui/common.py b/pyanaconda/ui/common.py index c583b7a..937171a 100644 --- a/pyanaconda/ui/common.py +++ b/pyanaconda/ui/common.py @@ -483,11 +483,15 @@ class Hub(UIObject): """ UIObject.__init__(self, data)
- self._spokes = {} self.storage = storage self.payload = payload self.instclass = instclass + self.paths = {} + self._spokes = {} + + # spokes for which environments this hub should collect? + self._environs = [ANACONDA_ENVIRON]
def set_path(self, path_id, paths): """Update the paths attribute with list of tuples in the form (module diff --git a/pyanaconda/ui/gui/hubs/__init__.py b/pyanaconda/ui/gui/hubs/__init__.py index b746608..3042b51 100644 --- a/pyanaconda/ui/gui/hubs/__init__.py +++ b/pyanaconda/ui/gui/hubs/__init__.py @@ -167,8 +167,8 @@ class Hub(GUIObject, common.Hub):
selectors = [] for spokeClass in sorted(cats_and_spokes[c], key=lambda s: s.title): - # Check if this spoke is to be shown in anaconda - if not spokeClass.should_run(ANACONDA_ENVIRON, self.data): + # Check if this spoke is to be shown in the supported environments + if not any(spokeClass.should_run(environ, self.data) for environ in self._environs): continue
# Create the new spoke and populate its UI with whatever data.
Spokes that should appear in both the installer and the initial-setup during the first boot should run only if 'firstboot --reconfig' is specified in the kickstart. But firstboot-only spokes should always run by default during the first boot, because otherwise they wouldn't be shown at all. The should_run classmethod can be overriden if needed, of course.
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/ui/common.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/pyanaconda/ui/common.py b/pyanaconda/ui/common.py index 937171a..91d878f 100644 --- a/pyanaconda/ui/common.py +++ b/pyanaconda/ui/common.py @@ -168,13 +168,8 @@ class FirstbootOnlySpokeMixIn(object): the data argument. """
- if environment == FIRSTBOOT_ENVIRON and data is None: - # cannot decide, stay in the game and let another call with data - # available (will come) decide - return True - elif environment == FIRSTBOOT_ENVIRON and \ - data and data.firstboot.firstboot == FIRSTBOOT_RECONFIG: - # generally run spokes in firstboot only if doing reconfig, spokes + if environment == FIRSTBOOT_ENVIRON: + # firstboot only spokes should run in firstboot by default, spokes # that should run even if not doing reconfig should override this # method return True
These 2 look ok to me.
On Tue, 2013-09-03 at 14:44 -0700, Brian C. Lane wrote:
These 2 look ok to me.
Oh, I forgot, these should go to the rhel7-branch as well, RHEL 7 I-S needs them.
On Tue, Sep 03, 2013 at 04:14:51PM +0200, Vratislav Podzimek wrote:
Patches 1/3 and 2/3 fix things that are needed for the EULA spoke to be collected and shown by the hub. PATCH 2/3 requires the changes made in the related anaconda patches. PATCH 3/3 adds the EULA spoke. The screenshots of the spoke can be found at:
http://vpodzime.fedorapeople.org/eula_i-s/
I don't know into which category the spoke should be put, but I don't want to add another category just for this one. Any suggestions? Shall we merge it into one of the existing categories and rename it? Or can EULA agreement be considered part of User Settings?
Or should it be a standalone spoke before the hub?
anaconda-patches@lists.fedorahosted.org