Adding persisting of credentials
authorEd Page <epage@Dulcinea.(none)>
Tue, 14 Apr 2009 02:13:06 +0000 (21:13 -0500)
committerEd Page <epage@Dulcinea.(none)>
Tue, 14 Apr 2009 02:13:06 +0000 (21:13 -0500)
src/doneit.glade
src/doneit.py
src/doneit_glade.py
src/gtk_null.py
src/gtk_rtmilk.py

index 350ef5c..044873c 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.5 on Fri Apr 10 14:52:59 2009 -->
+<!--Generated with glade3 3.4.5 on Mon Apr 13 18:40:41 2009 -->
 <glade-interface>
   <widget class="GtkWindow" id="mainWindow">
     <property name="default_width">800</property>
                       </widget>
                     </child>
                     <child>
+                      <widget class="GtkImageMenuItem" id="disconnectMenuItem">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">gtk-disconnect</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                      </widget>
+                    </child>
+                    <child>
                       <widget class="GtkImageMenuItem" id="quitMenuItem">
                         <property name="visible">True</property>
                         <property name="label" translatable="yes">gtk-quit</property>
         <property name="visible">True</property>
         <property name="spacing">2</property>
         <child>
-          <widget class="GtkComboBox" id="serviceCombo">
-            <property name="visible">True</property>
-          </widget>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
           <widget class="GtkTable" id="table1">
             <property name="visible">True</property>
             <property name="n_rows">2</property>
             <property name="n_columns">2</property>
             <child>
-              <widget class="GtkLabel" id="username_label">
-                <property name="visible">True</property>
-                <property name="label" translatable="yes">Username</property>
-              </widget>
-            </child>
-            <child>
-              <widget class="GtkLabel" id="password_label">
+              <widget class="GtkEntry" id="passwordentry">
                 <property name="visible">True</property>
-                <property name="label" translatable="yes">Password</property>
+                <property name="can_focus">True</property>
+                <property name="visibility">False</property>
               </widget>
               <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
                 <property name="top_attach">1</property>
                 <property name="bottom_attach">2</property>
               </packing>
               </packing>
             </child>
             <child>
-              <widget class="GtkEntry" id="passwordentry">
+              <widget class="GtkLabel" id="password_label">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="visibility">False</property>
+                <property name="label" translatable="yes">Password</property>
               </widget>
               <packing>
-                <property name="left_attach">1</property>
-                <property name="right_attach">2</property>
                 <property name="top_attach">1</property>
                 <property name="bottom_attach">2</property>
               </packing>
             </child>
+            <child>
+              <widget class="GtkLabel" id="username_label">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Username</property>
+              </widget>
+            </child>
           </widget>
           <packing>
             <property name="position">1</property>
           </packing>
         </child>
+        <child>
+          <widget class="GtkComboBox" id="serviceCombo">
+            <property name="visible">True</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
         <child internal-child="action_area">
           <widget class="GtkHButtonBox" id="dialog-action_area1">
             <property name="visible">True</property>
                 <property name="n_rows">3</property>
                 <property name="n_columns">2</property>
                 <child>
-                  <widget class="GtkHBox" id="edit-hbox2">
+                  <widget class="GtkLabel" id="edit-nameLabel">
                     <property name="visible">True</property>
-                    <child>
-                      <widget class="GtkEntry" id="edit-taskNameEntry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="has_focus">True</property>
-                        <property name="is_focus">True</property>
-                        <property name="can_default">True</property>
-                        <property name="has_default">True</property>
-                        <property name="receives_default">True</property>
-                      </widget>
-                    </child>
-                    <child>
-                      <widget class="GtkHButtonBox" id="edit-hbuttonbox2">
-                        <property name="visible">True</property>
-                        <child>
-                          <widget class="GtkButton" id="edit-pasteTaskNameButton">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">True</property>
-                            <property name="label" translatable="yes">gtk-paste</property>
-                            <property name="use_stock">True</property>
-                            <property name="response_id">0</property>
-                          </widget>
-                        </child>
-                      </widget>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
+                    <property name="label" translatable="yes">Name</property>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="edit-priorityLabel">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Priority</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="edit-dueDateLabel">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Due Date</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkComboBox" id="edit-priorityChoiceCombo">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="items" translatable="yes">None
+1
+2
+3</property>
                   </widget>
                   <packing>
                     <property name="left_attach">1</property>
                     <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="y_options"></property>
                   </packing>
                 </child>
                 <child>
                   </packing>
                 </child>
                 <child>
-                  <widget class="GtkComboBox" id="edit-priorityChoiceCombo">
+                  <widget class="GtkHBox" id="edit-hbox2">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="items" translatable="yes">None
-1
-2
-3</property>
+                    <child>
+                      <widget class="GtkEntry" id="edit-taskNameEntry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="has_focus">True</property>
+                        <property name="is_focus">True</property>
+                        <property name="can_default">True</property>
+                        <property name="has_default">True</property>
+                        <property name="receives_default">True</property>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkHButtonBox" id="edit-hbuttonbox2">
+                        <property name="visible">True</property>
+                        <child>
+                          <widget class="GtkButton" id="edit-pasteTaskNameButton">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="label" translatable="yes">gtk-paste</property>
+                            <property name="use_stock">True</property>
+                            <property name="response_id">0</property>
+                          </widget>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
                   </widget>
                   <packing>
                     <property name="left_attach">1</property>
                     <property name="right_attach">2</property>
-                    <property name="top_attach">1</property>
-                    <property name="bottom_attach">2</property>
-                    <property name="y_options"></property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkLabel" id="edit-dueDateLabel">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Due Date</property>
-                  </widget>
-                  <packing>
-                    <property name="top_attach">2</property>
-                    <property name="bottom_attach">3</property>
                   </packing>
                 </child>
-                <child>
-                  <widget class="GtkLabel" id="edit-priorityLabel">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Priority</property>
-                  </widget>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="bottom_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkLabel" id="edit-nameLabel">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Name</property>
-                  </widget>
-                </child>
               </widget>
               <packing>
                 <property name="position">1</property>
index 662f39e..e7ddec6 100755 (executable)
@@ -9,4 +9,4 @@ sys.path.insert(0,"/usr/lib/doneit/")
 import doneit_glade
 
 
-doneit.run_doneit()
+doneit_glade.run_doneit()
index 132155c..eef0c6f 100755 (executable)
@@ -9,6 +9,7 @@ import gc
 import os
 import threading
 import warnings
+import ConfigParser
 
 import gtk
 import gtk.glade
@@ -38,11 +39,13 @@ class DoneIt(object):
        _user_settings = "%s/settings.ini" % _user_data
 
        def __init__(self):
-               self._todoUIs = []
+               self._todoUIs = {}
                self._todoUI = None
                self._osso = None
                self._deviceIsOnline = True
                self._connection = None
+               self._fallbackUIName = ""
+               self._defaultUIName = ""
 
                for path in self._glade_files:
                        if os.path.isfile(path):
@@ -104,13 +107,12 @@ class DoneIt(object):
        def _idle_setup(self):
                # Barebones UI handlers
                import gtk_null
-               gtk.gdk.threads_enter()
-               try:
-                       self._todoUIs = [
-                               gtk_null.GtkNull(self._widgetTree),
-                       ]
-               finally:
-                       gtk.gdk.threads_leave()
+               with gtk_toolbox.gtk_lock():
+                       nullView = gtk_null.GtkNull(self._widgetTree)
+                       self._todoUIs[nullView.name()] = nullView
+                       self._todoUI = nullView
+                       self._todoUI.enable()
+                       self._fallbackUIName = nullView.name()
 
                # Setup maemo specifics
                try:
@@ -139,17 +141,20 @@ class DoneIt(object):
 
                # Setup costly backends
                import gtk_rtmilk
-               gtk.gdk.threads_enter()
-               try:
-                       self._todoUIs.extend([
-                               gtk_rtmilk.GtkRtMilk(self._widgetTree),
-                       ])
-                       self._todoUI = self._todoUIs[1]
-                       self._todoUI.enable()
-               finally:
-                       gtk.gdk.threads_leave()
+               with gtk_toolbox.gtk_lock():
+                       rtmView = gtk_rtmilk.GtkRtMilk(self._widgetTree)
+               self._todoUIs[rtmView.name()] = rtmView
+               self._defaultUIName = rtmView.name()
+
+               config = ConfigParser.SafeConfigParser()
+               config.read(self._user_settings)
+               with gtk_toolbox.gtk_lock():
+                       self.load_settings(config)
 
        def display_error_message(self, msg):
+               """
+               @note UI Thread
+               """
                error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
 
                def close(dialog, response, editor):
@@ -158,6 +163,75 @@ class DoneIt(object):
                error_dialog.connect("response", close, self)
                error_dialog.run()
 
+       def load_settings(self, config):
+               """
+               @note UI Thread
+               """
+               for todoUI in self._todoUIs.itervalues():
+                       try:
+                               todoUI.load_settings(config)
+                       except ConfigParser.NoSectionError, e:
+                               warnings.warn(
+                                       "Settings file %s is missing section %s" % (
+                                               self._user_settings,
+                                               e.section,
+                                       ),
+                                       stacklevel=2
+                               )
+
+               try:
+                       activeUIName = config.get(self.__pretty_app_name__, "active")
+               except ConfigParser.NoSectionError, e:
+                       activeUIName = ""
+                       warnings.warn(
+                               "Settings file %s is missing section %s" % (
+                                       self._user_settings,
+                                       e.section,
+                               ),
+                               stacklevel=2
+                       )
+
+               try:
+                       self._switch_ui(activeUIName)
+               except KeyError, e:
+                       self._switch_ui(self._defaultUIName)
+
+       def save_settings(self, config):
+               """
+               @note Thread Agnostic
+               """
+               config.add_section(self.__pretty_app_name__)
+               config.set(self.__pretty_app_name__, "active", self._todoUI.name())
+
+               for todoUI in self._todoUIs.itervalues():
+                       todoUI.save_settings(config)
+
+       def _switch_ui(self, uiName):
+               """
+               @note UI Thread
+               """
+               newActiveUI = self._todoUIs[uiName]
+               try:
+                       newActiveUI.login()
+               except RuntimeError:
+                       return # User cancelled the operation
+
+               self._todoUI.disable()
+               self._todoUI = newActiveUI
+               self._todoUI.enable()
+
+               if uiName != self._fallbackUIName:
+                       self._defaultUIName = uiName
+
+       def _save_settings(self):
+               """
+               @note Thread Agnostic
+               """
+               config = ConfigParser.SafeConfigParser()
+               self.save_settings(config)
+               with open(self._user_settings, "wb") as configFile:
+                       config.write(configFile)
+
        def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
                """
                For system_inactivity, we have no background tasks to pause
@@ -168,7 +242,7 @@ class DoneIt(object):
                        gc.collect()
 
                if save_unsaved_data or shutdown:
-                       pass
+                       self._save_settings()
 
        def _on_connection_change(self, connection, event, magicIdentifier):
                """
@@ -183,8 +257,10 @@ class DoneIt(object):
 
                if status == conic.STATUS_CONNECTED:
                        self._deviceIsOnline = True
+                       self._switch_ui(self._defaultUIName)
                elif status == conic.STATUS_DISCONNECTED:
                        self._deviceIsOnline = False
+                       self._switch_ui(self._fallbackUIName)
 
        def _on_window_state_change(self, widget, event, *args):
                """
@@ -196,11 +272,11 @@ class DoneIt(object):
                        self._isFullScreen = False
 
        def _on_close(self, *args, **kwds):
-               if self._osso is not None:
-                       self._osso.close()
-
                try:
-                       pass
+                       if self._osso is not None:
+                               self._osso.close()
+
+                       self._save_settings()
                finally:
                        gtk.main_quit()
 
@@ -217,6 +293,10 @@ class DoneIt(object):
                        else:
                                self.__window.fullscreen()
 
+       def _on_logout(self, *args):
+               self._todoUI.logout()
+               self._switch_ui(self._fallbackUIName)
+
        def _on_about_activate(self, *args):
                dlg = gtk.AboutDialog()
                dlg.set_name(self.__pretty_app_name__)
index 04b537c..ab33b0b 100644 (file)
@@ -7,40 +7,37 @@ class GtkNull(object):
                """
                @note Thread agnostic
                """
-               self._todoItemTree = widgetTree.get_widget("todoItemTree")
-               self._todoDetailsTree = widgetTree.get_widget("todoDetailsTree")
-               self._todoDetailsScroll = widgetTree.get_widget("todoDetailsScroll")
+               self._projectsCombo = widgetTree.get_widget("projectsCombo")
+               self._addTaskButton = widgetTree.get_widget("add-addTaskButton")
 
-               self._completeButton = widgetTree.get_widget("completeButton")
-               self._editButton = widgetTree.get_widget("editButton")
-               self._addButton = widgetTree.get_widget("addButton")
-
-               self._manager = None
+               self._manager = null.NullManager("", "")
 
        @staticmethod
        def name():
                return "None"
 
+       def load_settings(self, config):
+               pass
+
+       def save_settings(self, config):
+               pass
+
+       def login(self):
+               pass
+
+       def logout(self):
+               pass
+
        def enable(self):
                """
                @note UI Thread
                """
-               self._manager = null.NullManager("", "")
-
-               self._todoDetailsScroll.hide()
-
-               self._completeButton.set_sensitive(False)
-               self._editButton.set_sensitive(False)
-               self._addButton.set_sensitive(False)
+               self._projectsCombo.set_sensitive(False)
+               self._addTaskButton.set_sensitive(False)
 
        def disable(self):
                """
                @note UI Thread
                """
-               self._todoDetailsScroll.hide()
-
-               self._completeButton.set_sensitive(True)
-               self._editButton.set_sensitive(True)
-               self._addButton.set_sensitive(True)
-
-               self._manager = None
+               self._projectsCombo.set_sensitive(True)
+               self._addTaskButton.set_sensitive(True)
index 54508e7..bab46aa 100644 (file)
@@ -1,6 +1,7 @@
 import webbrowser
 import datetime
 import urlparse
+import base64
 
 import gobject
 import gtk
@@ -54,11 +55,11 @@ def get_token(username, apiKey, secret):
        return token
 
 
-def start_session(credentialsDialog):
+def get_credentials(credentialsDialog):
        # @todo Figure out storage of credentials
        username, password = credentialsDialog.request_credentials()
        token = get_token(username, rtmilk.RtMilkManager.API_KEY, rtmilk.RtMilkManager.SECRET)
-       return rtmilk.RtMilkManager(username, password, token)
+       return username, password, token
 
 
 class GtkRtMilk(object):
@@ -138,19 +139,61 @@ class GtkRtMilk(object):
                self._onClearId = None
                self._onPasteId = None
 
-               self._credentials = gtk_toolbox.LoginWindow(widgetTree)
+               self._credentialsDialog = gtk_toolbox.LoginWindow(widgetTree)
+               self._credentials = "", "", ""
                self._manager = None
 
        @staticmethod
        def name():
                return "Remember The Milk"
 
-       def enable(self):
+       def load_settings(self, config):
+               """
+               @note Thread Agnostic
+               """
+               username = config.get(self.name(), "username")
+               password = None
+               blobbedToken = config.get(self.name(), "bin_blob")
+               token = base64.b64decode(blobbedToken)
+               self._credentials = username, password, token
+
+       def save_settings(self, config):
+               """
+               @note Thread Agnostic
+               """
+               config.add_section(self.name())
+               config.set(self.name(), "username", self._credentials[0])
+               blobbedToken = base64.b64encode(self._credentials[2])
+               config.set(self.name(), "bin_blob", blobbedToken)
+
+       def login(self):
                """
                @note UI Thread
                """
-               self._manager = start_session(self._credentials)
+               if self._manager is not None:
+                       return
+
+               credentials = self._credentials
+               while True:
+                       try:
+                               self._manager = rtmilk.RtMilkManager(*credentials)
+                               self._credentials = credentials
+                               return # Login succeeded
+                       except rtmapi.AuthStateMachine.NoData:
+                               # Login failed, grab new credentials
+                               credentials = get_credentials(self._credentialsDialog)
+
+       def logout(self):
+               """
+               @note Thread Agnostic
+               """
+               self._credentials = "", "", ""
+               self._manager = None
 
+       def enable(self):
+               """
+               @note UI Thread
+               """
                self._projectsList.clear()
                self._populate_projects()