[PATCH 3/3] Add button and dialog for NTP servers configuration

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Providing and option to use NTP we also need to allow users to set up
NTP servers, because default servers may not be reachable, there could
be internal NTP servers etc.
---
 pyanaconda/ui/gui/spokes/datetime_spoke.py |  223 ++++++++++++++++++++++-
 pyanaconda/ui/gui/spokes/datetime_spoke.ui |  274 +++++++++++++++++++++++++++-
 2 files changed, 493 insertions(+), 4 deletions(-)

diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.py b/pyanaconda/ui/gui/spokes/datetime_spoke.py
index 673daf7..b590dc5 100644
--- a/pyanaconda/ui/gui/spokes/datetime_spoke.py
+++ b/pyanaconda/ui/gui/spokes/datetime_spoke.py
@@ -28,17 +28,212 @@ from gi.repository import AnacondaWidgets, GLib, Gtk
 from pyanaconda.ui.gui import UIObject
 from pyanaconda.ui.gui.spokes import NormalSpoke
 from pyanaconda.ui.gui.categories.localization import LocalizationCategory
+from pyanaconda.ui.gui.utils import enlightbox
 
-from pyanaconda import localization, iutil, network
-import datetime, os
+from pyanaconda import localization, iutil, network, ntp
+from pyanaconda.threads import threadMgr, AnacondaThread
+
+import datetime, os, threading
 
 __all__ = ["DatetimeSpoke"]
 
+SERVER_OK = 0
+SERVER_NOK = 1
+SERVER_QUERY = 2
+
+class NTPconfigDialog(UIObject):
+    builderObjects = ["ntpConfigDialog", "addImage", "serversStore"]
+    mainWidgetName = "ntpConfigDialog"
+    uiFile = "spokes/datetime_spoke.ui"
+
+    def __init__(self, *args):
+        UIObject.__init__(self, *args)
+
+        #used to ensure uniqueness of the threads' names
+        self._threads_counter = 0
+
+        #epoch is increased when serversStore is repopulated
+        self._epoch = 0
+        self._epoch_lock = threading.Lock()
+
+    @property
+    def working_server(self):
+        itr = self._serversStore.get_iter_first()
+        while itr:
+            row = self._serversStore[itr]
+            if row[1] == SERVER_OK and row[2]:
+                #server is checked and working
+                return row[0]
+
+            itr = self._serversStore.iter_next(itr)
+
+        return None
+
+    def _render_working(self, column, renderer, model, itr, user_data=None):
+        #get the value in the second column
+        value = model[itr][1]
+
+        if value == SERVER_QUERY:
+            renderer.set_property("stock-id", "gtk-dialog-question")
+        elif value == SERVER_OK:
+            renderer.set_property("stock-id", "gtk-yes")
+        else:
+            renderer.set_property("stock-id", "gtk-no")
+
+    def initialize(self):
+        self.window.set_size_request(500, 400)
+
+        workingColumn = self.builder.get_object("workingColumn")
+        workingRenderer = self.builder.get_object("workingRenderer")
+        workingColumn.set_cell_data_func(workingRenderer, self._render_working)
+
+        self._serversStore = self.builder.get_object("serversStore")
+        self._initialize_store_from_config()
+
+        self._serverEntry = self.builder.get_object("serverEntry")
+
+    def _initialize_store_from_config(self):
+        self._serversStore.clear()
+        for server in ntp.get_servers_from_config():
+            self._add_server(server)
+
+    def refresh(self):
+        self._serverEntry.grab_focus()
+
+    def refresh_servers_state(self):
+        itr = self._serversStore.get_iter_first()
+        while itr:
+            self._refresh_server_working(itr)
+            itr = self._serversStore.iter_next(itr)
+
+    def run(self):
+        self.window.show()
+        rc = self.window.run()
+        self.window.hide()
+
+        #OK clicked
+        if rc == 1:
+            new_servers = list()
+
+            itr = self._serversStore.get_iter_first()
+            while itr:
+                row = self._serversStore[itr]
+
+                #if server checked
+                if row[2]:
+                    new_servers.append(row[0])
+
+                itr = self._serversStore.iter_next(itr)
+
+            ntp.save_servers_to_config(new_servers)
+            iutil.restart_service("chronyd")
+
+        #Cancel clicked, window destroyed...
+        else:
+            self._epoch_lock.acquire()
+            self._epoch += 1
+            self._epoch_lock.release()
+
+            self._initialize_store_from_config()
+
+        return rc
+
+    def _set_server_ok_nok(self, itr, epoch_started):
+        """
+        If the server is working, set its data to SERVER_OK, otherwise set its
+        data to SERVER_NOK.
+
+        @param itr: iterator of the $server's row in the self._serversStore
+
+        """
+
+        def set_store_value(arg_tuple):
+            """
+            We need a function for this, because this way it can be added to
+            the MainLoop with thread-safe GLib.idle_add (but only with one
+            argument).
+
+            @param arg_tuple: (store, itr, column, value)
+
+            """
+
+            (store, itr, column, value) = arg_tuple
+            store.set_value(itr, column, value)
+
+        orig_hostname = self._serversStore[itr][0]
+        server_working = ntp.ntp_server_working(self._serversStore[itr][0])
+
+        #do not let dialog change epoch while we are modifying data
+        self._epoch_lock.acquire()
+
+        #check if we are in the same epoch as the dialog (and the serversStore)
+        #and if the server wasn't changed meanwhile
+        if epoch_started == self._epoch:
+            actual_hostname = self._serversStore[itr][0]
+
+            if orig_hostname == actual_hostname:
+                if server_working:
+                    GLib.idle_add(set_store_value, (self._serversStore,
+                                                    itr, 1, SERVER_OK))
+                else:
+                    GLib.idle_add(set_store_value, (self._serversStore,
+                                                    itr, 1, SERVER_NOK))
+        self._epoch_lock.release()
+
+    def _refresh_server_working(self, itr):
+        """ Runs a new thread with _set_server_ok_nok(itr) as a taget. """
+
+        self._serversStore.set_value(itr, 1, SERVER_QUERY)
+        new_thread_name = "AnaNTPserver%d" % self._threads_counter
+        threadMgr.add(AnacondaThread(name=new_thread_name,
+                                     target=self._set_server_ok_nok,
+                                     args=(itr, self._epoch)))
+        self._threads_counter += 1
+
+    def _add_server(self, server):
+        itr = self._serversStore.get_iter_first()
+        while itr:
+            if self._serversStore[itr][0] == server:
+                #do not add duplicate items
+                return
+
+            itr = self._serversStore.iter_next(itr)
+
+        itr = self._serversStore.append([server, SERVER_QUERY, True])
+
+        #do not block UI while starting thread (may take some time)
+        GLib.idle_add(self._refresh_server_working, itr)
+
+    def on_entry_activated(self, entry, *args):
+        self._add_server(entry.get_text())
+        entry.set_text("")
+
+    def on_add_clicked(self, *args):
+        self._add_server(self._serverEntry.get_text())
+        self._serverEntry.set_text("")
+
+    def on_use_server_toggled(self, renderer, path, *args):
+        itr = self._serversStore.get_iter(path)
+        old_value = self._serversStore[itr][2]
+
+        self._serversStore.set_value(itr, 2, not old_value)
+
+    def on_server_edited(self, renderer, path, new_text, *args):
+        itr = self._serversStore.get_iter(path)
+
+        if self._serversStore[itr][0] == new_text:
+            return
+
+        self._serversStore.set_value(itr, 0, new_text)
+        self._serversStore.set_value(itr, 1, SERVER_QUERY)
+
+        self._refresh_server_working(itr)
+
 class DatetimeSpoke(NormalSpoke):
     builderObjects = ["datetimeWindow",
                       "days", "months", "years", "regions", "cities",
                       "upImage", "upImage1", "upImage2", "downImage",
-                      "downImage1", "downImage2", "downImage3",
+                      "downImage1", "downImage2", "downImage3", "configImage",
                       "citiesFilter", "daysFilter", "citiesSort", "regionsSort",
                       ]
 
@@ -114,6 +309,9 @@ class DatetimeSpoke(NormalSpoke):
         else:
             self._tzmap.set_timezone("Europe/Prague")
 
+        self._config_dialog = NTPconfigDialog(self.data)
+        self._config_dialog.initialize()
+
     @property
     def status(self):
         if self.data.timezone.timezone:
@@ -148,6 +346,7 @@ class DatetimeSpoke(NormalSpoke):
             self._show_no_network_warning()
         else:
             self.window.clear_info()
+            self._config_dialog.refresh_servers_state()
 
         ntp_working = has_active_network and iutil.service_running("chronyd")
 
@@ -490,6 +689,10 @@ class DatetimeSpoke(NormalSpoke):
                              _("You need to set up network first if you "\
                                "want to use NTP"))
 
+    def _show_no_ntp_server_warning(self):
+        self.window.set_info(Gtk.MessageType.WARNING,
+                             _("You have no working NTP server configured"))
+
     def on_ntp_switched(self, switch, *args):
         #turned ON
         if switch.get_active():
@@ -499,6 +702,8 @@ class DatetimeSpoke(NormalSpoke):
                 return
             else:
                 self.window.clear_info()
+                if self._config_dialog.working_server is None:
+                    self._show_no_ntp_server_warning()
 
             ret = iutil.start_service("chronyd")
             self._set_date_time_setting_sensitive(False)
@@ -518,3 +723,15 @@ class DatetimeSpoke(NormalSpoke):
             if (ret != 0) and iutil.service_running("chronyd"):
                 switch.set_active(True)
 
+    def on_ntp_config_clicked(self, *args):
+        self._config_dialog.refresh()
+
+        with enlightbox(self.window, self._config_dialog.window):
+            response = self._config_dialog.run()
+
+        if response == 1:
+            if self._config_dialog.working_server is None:
+                self._show_no_ntp_server_warning()
+            else:
+                self.window.clear_info()
+
diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.ui b/pyanaconda/ui/gui/spokes/datetime_spoke.ui
index f705d2b..7a6d3b2 100644
--- a/pyanaconda/ui/gui/spokes/datetime_spoke.ui
+++ b/pyanaconda/ui/gui/spokes/datetime_spoke.ui
@@ -14,11 +14,21 @@
   <object class="GtkTreeModelSort" id="citiesSort">
     <property name="model">citiesFilter</property>
   </object>
+  <object class="GtkImage" id="configImage">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="icon_name">system-run-symbolic</property>
+  </object>
+  <object class="GtkImage" id="addImage">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="icon_name">list-add-symbolic</property>
+  </object>
   <object class="AnacondaSpokeWindow" id="datetimeWindow">
     <property name="startup_id">filler</property>
-    <property name="window_name">DATE &amp; TIME</property>
     <property name="can_focus">False</property>
     <property name="startup_id">filler</property>
+    <property name="window_name">DATE &amp; TIME</property>
     <signal name="back-clicked" handler="on_back_clicked" swapped="no"/>
     <child internal-child="main_box">
       <object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
@@ -31,6 +41,8 @@
             <property name="margin_left">6</property>
             <property name="margin_right">6</property>
             <property name="margin_top">6</property>
+            <property name="n_rows">2</property>
+            <property name="n_columns">2</property>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -161,6 +173,28 @@
                                         <property name="use_action_appearance">False</property>
                                         <signal name="notify::active" handler="on_ntp_switched" swapped="no"/>
                                       </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">1</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkButton" id="ntpConfigButton">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="use_action_appearance">False</property>
+                                        <property name="image">configImage</property>
+                                        <signal name="clicked" handler="on_ntp_config_clicked" swapped="no"/>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">1</property>
+                                        <property name="position">2</property>
+                                      </packing>
                                     </child>
                                   </object>
                                 </child>
@@ -576,6 +610,234 @@
   <object class="GtkTreeModelFilter" id="daysFilter">
     <property name="child_model">days</property>
   </object>
+  <object class="GtkDialog" id="ntpConfigDialog">
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkAlignment" id="alignment3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="yscale">0.80000001192092896</property>
+            <child>
+              <object class="GtkBox" id="box7">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="configHeadingLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Add and mark for usage NTP servers</property>
+                    <attributes>
+                      <attribute name="font-desc" value="Sans Bold 12"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xscale">0.80000001192092896</property>
+                    <property name="yscale">0.80000001192092896</property>
+                    <child>
+                      <object class="GtkBox" id="box2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <object class="GtkBox" id="box3">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <child>
+                              <object class="GtkEntry" id="serverEntry">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="invisible_char">●</property>
+                                <property name="invisible_char_set">True</property>
+                                <signal name="activate" handler="on_entry_activated" swapped="no"/>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="addButton">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="use_action_appearance">False</property>
+                                <property name="image">addImage</property>
+                                <signal name="clicked" handler="on_add_clicked" swapped="no"/>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="padding">4</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="scrolledWindow">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="hscrollbar_policy">never</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTreeView" id="serversView">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="model">serversStore</property>
+                                <property name="headers_clickable">False</property>
+                                <property name="search_column">0</property>
+                                <child internal-child="selection">
+                                  <object class="GtkTreeSelection" id="treeview-selection"/>
+                                </child>
+                                <child>
+                                  <object class="GtkTreeViewColumn" id="hostnameColumn">
+                                    <property name="title" translatable="yes">Hostname</property>
+                                    <property name="expand">True</property>
+                                    <child>
+                                      <object class="GtkCellRendererText" id="hostnameRenderer">
+                                        <property name="editable">True</property>
+                                        <signal name="edited" handler="on_server_edited" swapped="no"/>
+                                      </object>
+                                      <attributes>
+                                        <attribute name="text">0</attribute>
+                                      </attributes>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkTreeViewColumn" id="workingColumn">
+                                    <property name="title" translatable="yes">Working</property>
+                                    <child>
+                                      <object class="GtkCellRendererPixbuf" id="workingRenderer"/>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkTreeViewColumn" id="removeColumn">
+                                    <property name="title" translatable="yes">Use</property>
+                                    <child>
+                                      <object class="GtkCellRendererToggle" id="removeRenderer">
+                                        <signal name="toggled" handler="on_use_server_toggled" swapped="no"/>
+                                      </object>
+                                      <attributes>
+                                        <attribute name="active">2</attribute>
+                                      </attributes>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="poolsNote">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Note: pool servers may not be available all the time</property>
+                    <attributes>
+                      <attribute name="font-desc" value="Sans Italic 9"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancelButton">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="okButton">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </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="pack_type">end</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">cancelButton</action-widget>
+      <action-widget response="1">okButton</action-widget>
+    </action-widgets>
+  </object>
   <object class="GtkImage" id="downImage">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -603,6 +865,16 @@
       <column type="gchararray"/>
     </columns>
   </object>
+  <object class="GtkListStore" id="serversStore">
+    <columns>
+      <!-- column-name hostname -->
+      <column type="gchararray"/>
+      <!-- column-name working -->
+      <column type="gint"/>
+      <!-- column-name use -->
+      <column type="gboolean"/>
+    </columns>
+  </object>
   <object class="GtkImage" id="upImage">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
-- 
1.7.4.4

_______________________________________________
Anaconda-devel-list mailing list
Anaconda-devel-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/anaconda-devel-list



[Index of Archives]     [Kickstart]     [Fedora Users]     [Fedora Legacy List]     [Fedora Maintainers]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]
  Powered by Linux