[postr] Added error logging to every Twisted callback



commit eadde207b344bd3778a3e331cef2c1276daa27d9
Author: GermÃn Poo-CaamaÃo <gpoo gnome org>
Date:   Thu Oct 4 01:56:31 2012 -0700

    Added error logging to every Twisted callback
    
    - Error callbacks return failure to avoid silently miss errors
    - Formatted the code to make it easier to read (chaining
      callbacks in Twisted is not very readable).
    
    Signed-off-by: GermÃn Poo-CaamaÃo <gpoo gnome org>

 src/GroupSelector.py |   14 +++++++++-
 src/LicenseCombo.py  |   14 ++++-------
 src/SetCombo.py      |   12 ++++++++-
 src/StatusBar.py     |    9 ++++--
 src/TagsEntry.py     |   27 ++++++++++-----------
 src/flickrest.py     |   64 +++++++++++++++++++++++++++++++++----------------
 src/postr.py         |   48 +++++++++++++++++++++++++++----------
 src/util.py          |   13 ++++++----
 8 files changed, 132 insertions(+), 69 deletions(-)
---
diff --git a/src/GroupSelector.py b/src/GroupSelector.py
index bf29365..6c32ac9 100644
--- a/src/GroupSelector.py
+++ b/src/GroupSelector.py
@@ -19,6 +19,8 @@ from gi.repository import Gtk, GObject, Pango, GdkPixbuf
 from ErrorDialog import ErrorDialog
 import util
 
+from twisted.python import log
+
 (COL_SELECTED,
  COL_ID,
  COL_NAME,
@@ -82,7 +84,9 @@ class GroupSelector(Gtk.TreeView):
 
     def update(self):
         # TODO: block changed signals
-        self.flickr.groups_pools_getGroups().addCallbacks(self.got_groups, self.twisted_error)
+        deferred = self.flickr.groups_pools_getGroups()
+        deferred.addCallback(self.got_groups)
+        deferred.addErrback(self.twisted_error)
 
     def got_groups(self, rsp):
         for group in rsp.findall("groups/group"):
@@ -92,13 +96,19 @@ class GroupSelector(Gtk.TreeView):
                             COL_NAME, group.get("name"))
             def got_thumb(thumb, it):
                 self.model.set (it, COL_ICON, thumb)
-            util.get_buddyicon(self.flickr, group, self.thumb_size).addCallback(got_thumb, it)
+
+            deferred = util.get_buddyicon(self.flickr, group, self.thumb_size)
+            deferred.addCallback(got_thumb, it)
+            deferred.addErrback(self.twisted_error)
 
     def twisted_error(self, failure):
         dialog = ErrorDialog()
         dialog.set_from_failure(failure)
         dialog.show_all()
 
+        log.err(failure, 'Exception in %s' % self.__gtype_name__)
+        return failure
+
     def get_selected_groups(self):
         return [row[COL_ID] for row in self.model if row[COL_SELECTED]]
 
diff --git a/src/LicenseCombo.py b/src/LicenseCombo.py
index a185eb4..5f30a1a 100644
--- a/src/LicenseCombo.py
+++ b/src/LicenseCombo.py
@@ -16,6 +16,7 @@
 # St, Fifth Floor, Boston, MA 02110-1301 USA
 
 from gi.repository import GObject, Gtk
+from twisted.python import log
 
 class LicenseCombo(Gtk.ComboBox):
     __gtype_name__ = 'LicenseCombo'
@@ -23,7 +24,7 @@ class LicenseCombo(Gtk.ComboBox):
     def __init__(self):
         Gtk.ComboBox.__init__(self)
         self.flickr = None
-        
+
         self.model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT)
         self.model.append([_("Default"), -1])
         self.set_model(self.model)
@@ -33,12 +34,6 @@ class LicenseCombo(Gtk.ComboBox):
         self.pack_start(cell, True)
         self.add_attribute(cell, "text", 0)
 
-    def twisted_error(self, failure):
-        from ErrorDialog import ErrorDialog
-        dialog = ErrorDialog()
-        dialog.set_from_failure(failure)
-        dialog.show_all()
-
     def __got_licenses(self, rsp):
         """Callback for the photos.licenses.getInfo call"""
         for license in rsp.findall("licenses/license"):
@@ -46,8 +41,9 @@ class LicenseCombo(Gtk.ComboBox):
             self.model.append([license.get("name"), license_id])
 
     def update(self):
-        self.flickr.photos_licenses_getInfo().addCallbacks(self.__got_licenses,
-                                                           self.twisted_error)
+        deferred = self.flickr.photos_licenses_getInfo()
+        deferred.addCallback(self.__got_licenses)
+        deferred.addErrback(log.err)
 
     def get_license_for_iter(self, it):
         if it is None: return None
diff --git a/src/SetCombo.py b/src/SetCombo.py
index 3b5cc1e..4cdc21c 100644
--- a/src/SetCombo.py
+++ b/src/SetCombo.py
@@ -20,6 +20,7 @@
 import datetime
 from gi.repository import GObject, Gtk, GdkPixbuf
 from twisted.web.client import getPage
+from twisted.python import log
 
 _NO_PHOTOSET_ID = "-1"
 _NO_PHOTOSET_LABEL = _("None")
@@ -71,6 +72,9 @@ class SetCombo(Gtk.ComboBox):
         dialog.set_from_failure(failure)
         dialog.show_all()
 
+        log.err(failure, 'Exception in %s' % self.__gtype_name__)
+        return failure
+
     def __got_set_thumb(self, page, it):
         loader = GdkPixbuf.PixbufLoader()
         loader.set_size (self.thumb_size, self.thumb_size)
@@ -87,10 +91,14 @@ class SetCombo(Gtk.ComboBox):
                            1, photoset.find("title").text)
 
             url = "http://static.flickr.com/%s/%s_%s%s.jpg"; % (photoset.get("server"), photoset.get("primary"), photoset.get("secret"), "_s")
-            getPage (url).addCallback(self.__got_set_thumb, it).addErrback(self.twisted_error)
+            deferred = getPage(url)
+            deferred.addCallback(self.__got_set_thumb, it)
+            deferred.addErrback(self.twisted_error)
 
     def update(self):
-        self.flickr.photosets_getList().addCallbacks(self.__got_photosets, self.twisted_error)
+        deferred = self.flickr.photosets_getList()
+        deferred.addCallback(self.__got_photosets)
+        deferred.addErrback(self.twisted_error)
 
     def get_id_for_iter(self, it):
         if it is None: return None
diff --git a/src/StatusBar.py b/src/StatusBar.py
index d0f956a..32eee67 100644
--- a/src/StatusBar.py
+++ b/src/StatusBar.py
@@ -19,6 +19,7 @@ from gi.repository import Gtk, GObject
 from ErrorDialog import ErrorDialog
 from util import greek
 from xml.sax.saxutils import escape
+from twisted.python import log
 
 class StatusBar(Gtk.Label):
     __gtype_name__ = 'StatusBar'
@@ -38,7 +39,7 @@ class StatusBar(Gtk.Label):
 
         if self.flickr.get_username():
             message = message + _("Logged in as <b>%s</b>.  ") % escape(self.flickr.get_fullname() or self.flickr.get_username())
-        
+
         if self.quota and self.to_upload:
             message = message + _("You can upload %(quota)s this month, and have %(to_upload)s to upload.") % self.__dict__
         elif self.quota:
@@ -47,7 +48,7 @@ class StatusBar(Gtk.Label):
             message = message + _("%(to_upload)s to upload.") % self.__dict__
 
         self.set_markup(message)
-    
+
     def update_quota(self):
         """Call Flickr to get the current upload quota, and update the status bar."""
         def got_quota(rsp):
@@ -61,7 +62,9 @@ class StatusBar(Gtk.Label):
             dialog = ErrorDialog(self.get_toplevel())
             dialog.set_from_failure(failure)
             dialog.show_all()
-        self.flickr.people_getUploadStatus().addCallbacks(got_quota, error)
+        deferred = self.flickr.people_getUploadStatus()
+        deferred.addCallback(got_quota)
+        deferred.addErrback(log.err)
 
     def set_upload(self, to_upload):
         """Set the amount of data to be uploaded, and update the status bar."""
diff --git a/src/TagsEntry.py b/src/TagsEntry.py
index abf6341..79c5a64 100644
--- a/src/TagsEntry.py
+++ b/src/TagsEntry.py
@@ -16,6 +16,7 @@
 # St, Fifth Floor, Boston, MA 02110-1301 USA
 
 from gi.repository import Gtk, GObject
+from twisted.python import log
 
 _USER_POPULAR_TAGS = 200
 _HOTS_TAGS = 20
@@ -57,7 +58,7 @@ class TagsEntry(Gtk.Entry):
             # add the matching word
             current_text = '%s %s ' % (current_text, model[iter][_COL_TAG_NAME])
         else:
-            current_text = model[iter][_COL_TAG_NAME] +' '
+            current_text = model[iter][_COL_TAG_NAME] + ' '
 
         # set back the whole text
         self.set_text(current_text)
@@ -83,15 +84,20 @@ class TagsEntry(Gtk.Entry):
         return modelstr.lower().startswith(key_string.lower())
 
     def update(self):
-
         self.completion_model.clear()
 
-        self.flickr.tags_getListUserPopular(user_id=self.flickr.get_nsid(), \
-        count=_USER_POPULAR_TAGS).addCallbacks(self.create_completion_model,
-        self.twisted_error)
+        # In both cases, we do not call a dialog to throw an error
+        # because it is too invasive when we query Flickr constantly.
+        user_id = self.flickr.get_nsid()
+        deferred1 = self.flickr.tags_getListUserPopular(user_id=user_id,
+                                                       count=_USER_POPULAR_TAGS)
+        deferred1.addCallback(self.create_completion_model)
+        deferred1.addErrback(log.err)
 
-        self.flickr.tags_getHotList(user_id=self.flickr.get_nsid(), count=_HOTS_TAGS)\
-        .addCallbacks(self.create_completion_model, self.twisted_error)
+        deferred2 = self.flickr.tags_getHotList(user_id=user_id,
+                                               count=_HOTS_TAGS)
+        deferred2.addCallback(self.create_completion_model)
+        deferred2.addErrback(log.err)
 
     def create_completion_model(self, rsp):
         '''
@@ -102,10 +108,3 @@ class TagsEntry(Gtk.Entry):
             self.completion_model.append([tag.text])
 
         self.completion.set_model(self.completion_model)
-
-    def twisted_error(self, failure):
-        #TODO: throw a message in a less invasive way
-        from ErrorDialog import ErrorDialog
-        dialog = ErrorDialog()
-        dialog.set_from_failure(failure)
-        dialog.show_all()
diff --git a/src/flickrest.py b/src/flickrest.py
index 9980dfd..9213591 100644
--- a/src/flickrest.py
+++ b/src/flickrest.py
@@ -19,6 +19,7 @@ import logging, os, mimetools, urllib
 from gi.repository import Gio
 from twisted.internet import defer
 from twisted.python.failure import Failure
+from twisted.python import log
 import proxyclient as client
 
 try:
@@ -36,7 +37,7 @@ class FlickrError(Exception):
         Exception.__init__(self)
         self.code = int(code)
         self.message = message
-    
+
     def __str__(self):
         return "%d: %s" % (self.code, self.message)
 
@@ -48,7 +49,7 @@ class FlickrError(Exception):
 
 class Flickr:
     endpoint = "http://api.flickr.com/services/rest/?";
-    
+
     def __init__(self, api_key, secret, perms="read"):
         self.__methods = {}
         self.api_key = api_key
@@ -75,10 +76,10 @@ class Flickr:
         if proxy and "://" not in proxy:
             proxy = "http://"; + proxy
         self.proxy = proxy
-    
+
     def __repr__(self):
         return "<FlickREST>"
-    
+
     def __getTokenFile(self):
         """Get the filename that contains the authentication token for the API key"""
         return os.path.expanduser(os.path.join("~", ".flickr", self.api_key, "auth.xml"))
@@ -92,7 +93,7 @@ class Flickr:
         if os.path.exists(token):
             os.remove(token)
         self.token = None
-    
+
     def __sign(self, kwargs):
         kwargs['api_key'] = self.api_key
         # If authenticating we don't yet have a token
@@ -112,7 +113,7 @@ class Flickr:
         return client.getPage(Flickr.endpoint, proxy=self.proxy, method="POST",
                               headers={"Content-Type": "application/x-www-form-urlencoded"},
                               postdata=urllib.urlencode(kwargs))
-    
+
     def __cb(self, data, method):
         self.logger.info("%s returned" % method)
         xml = ElementTree.XML(data)
@@ -124,12 +125,16 @@ class Flickr:
         else:
             # Fake an error in this case
             raise FlickrError(0, "Invalid response")
-    
+
     def __getattr__(self, method):
         method = "flickr." + method.replace("_", ".")
         if not self.__methods.has_key(method):
             def proxy(method=method, **kwargs):
-                return self.__call(method, kwargs).addCallback(self.__cb, method)
+                deferred = self.__call(method, kwargs)
+                deferred.addCallback(self.__cb, method)
+                deferred.addErrback(log.err)
+                return deferred
+
             self.__methods[method] = proxy
         return self.__methods[method]
 
@@ -161,7 +166,7 @@ class Flickr:
         # Add final boundary.
         lines.append("--" + boundary.encode("utf-8"))
         return (boundary, '\r\n'.join(lines))
-    
+
     def upload(self, uri=None, imageData=None,
                title=None, desc=None, tags=None,
                is_public=None, is_family=None, is_friend=None,
@@ -194,23 +199,27 @@ class Flickr:
             kwargs['content_type'] = content_type
         self.__sign(kwargs)
         self.logger.info("Upload args %s" % kwargs)
-        
+
         if imageData:
             kwargs['photo'] = imageData
         else:
             kwargs['photo'] = Gio.File.new_for_uri(uri)
 
         (boundary, form) = self.__encodeForm(kwargs)
-        headers= {
+        headers = {
             "Content-Type": "multipart/form-data; boundary=%s" % boundary,
             "Content-Length": str(len(form))
             }
 
         self.logger.info("Calling upload")
-        return client.upload("http://api.flickr.com/services/upload/";,
-                             proxy=self.proxy, method="POST",
-                             headers=headers, postdata=form,
-                             progress_tracker=progress_tracker).addCallback(self.__cb, "upload")
+        deferred = client.upload("http://api.flickr.com/services/upload/";,
+                                 proxy=self.proxy, method="POST",
+                                 headers=headers, postdata=form,
+                                 progress_tracker=progress_tracker)
+        deferred.addCallback(self.__cb, "upload")
+        deferred.addErrback(log.err)
+
+        return deferred
 
     def authenticate_2(self, state):
         def gotToken(e):
@@ -235,7 +244,11 @@ class Flickr:
 
             # Callback to the user
             return True
-        return self.auth_getToken(frob=state['frob']).addCallback(gotToken)
+        deferred = self.auth_getToken(frob=state['frob'])
+        deferred.addCallback(gotToken)
+        deferred.addErrback(log.err)
+
+        return deferred
 
     def __get_frob(self):
         """Make the getFrob() call."""
@@ -246,7 +259,12 @@ class Flickr:
             self.__sign(keys)
             url = "http://flickr.com/services/auth/?api_key=%(api_key)s&perms=%(perms)s&frob=%(frob)s&api_sig=%(api_sig)s" % keys
             return {'url': url, 'frob': frob}
-        return self.auth_getFrob().addCallback(gotFrob)
+
+        deferred = self.auth_getFrob()
+        deferred.addCallback(gotFrob)
+        deferred.addErrback(log.err)
+
+        return deferred
 
     def authenticate_1(self):
         """Attempts to log in to Flickr. The return value is a Twisted Deferred
@@ -263,7 +281,7 @@ class Flickr:
             try:
                 e = ElementTree.parse(filename).getroot()
                 self.token = e.find("auth/token").text
-                
+
                 user = e.find("auth/user")
                 self.fullname = user.get("fullname")
                 self.username = user.get("username")
@@ -275,13 +293,17 @@ class Flickr:
                     # If checkToken() failed, we need to re-authenticate
                     self.clear_cached()
                     return self.__get_frob()
-                return self.auth_checkToken().addCallbacks(reply, failed)
+                deferred = self.auth_checkToken()
+                deferred.addCallbacks(reply, failed)
+                deferred.addErrback(log.err)
+
+                return deferred
             except:
                 # TODO: print the exception to stderr?
                 pass
-            
+
         return self.__get_frob()
-    
+
     @staticmethod
     def get_photo_url(photo, size=SIZE_MEDIUM):
         if photo is None:
diff --git a/src/postr.py b/src/postr.py
index 33dc81d..b6f4be2 100644
--- a/src/postr.py
+++ b/src/postr.py
@@ -21,6 +21,8 @@ import logging, os, urllib
 from urlparse import urlparse
 from os.path import basename
 from tempfile import mkstemp
+from twisted.python import log
+from twisted.internet import reactor
 
 from gi.repository import GObject, Gtk, GConf, GdkPixbuf, Gio, Gdk, GLib
 
@@ -216,7 +218,9 @@ class Postr(UniqueApp):
         self.proxy_changed(client, 0, None, None)
 
         # Connect to flickr, go go go
-        self.flickr.authenticate_1().addCallbacks(self.auth_open_url, self.twisted_error)
+        deferred = self.flickr.authenticate_1()
+        deferred.addCallback(self.auth_open_url)
+        deferred.addErrback(self.twisted_error)
 
     def open_uri(self, uri):
         return self.send_message(self.commands['OPEN'], uri)
@@ -228,6 +232,9 @@ class Postr(UniqueApp):
         dialog.set_from_failure(failure)
         dialog.show_all()
 
+        log.err(failure, 'Exception in %s' % self.__gtype_name__)
+        return failure
+
     def proxy_changed(self, client, cnxn_id, entry, something):
         if client.get_bool("/system/http_proxy/use_http_proxy"):
             host = client.get_string("/system/http_proxy/host")
@@ -274,7 +281,9 @@ class Postr(UniqueApp):
         else:
             dialog = AuthenticationDialog(self.window, state['url'])
             if dialog.run() == Gtk.ResponseType.ACCEPT:
-                self.flickr.authenticate_2(state).addCallbacks(self.connected, self.twisted_error)
+                deferred = self.flickr.authenticate_2(state)
+                deferred.addCallback(self.connected)
+                deferred.addErrback(self.twisted_error)
             dialog.destroy()
 
     def connected(self, connected):
@@ -320,18 +329,24 @@ class Postr(UniqueApp):
         Update the avatar displayed at the top of the window.  Called when
         authentication is completed.
         """
-        def getinfo_cb(rsp):
+        def getinfo_cb(rsp, user_id):
             p = rsp.find("person")
             data = {
-                "nsid": self.flickr.get_nsid(),
+                "nsid": user_id,
                 "iconfarm": p.get("iconfarm"),
                 "iconserver": p.get("iconserver")
             }
             def get_buddyicon_cb(icon):
                 self.avatar_image.set_from_pixbuf(icon)
-            get_buddyicon(self.flickr, data).addCallbacks(get_buddyicon_cb, self.twisted_error)
+            deferred = get_buddyicon(self.flickr, data)
+            deferred.addCallback(get_buddyicon_cb)
+            deferred.addErrback(log.err)
+
         # Need to call people.getInfo to get the iconserver/iconfarm
-        self.flickr.people_getInfo(user_id=self.flickr.get_nsid()).addCallbacks(getinfo_cb, self.twisted_error)
+        user_id = self.flickr.get_nsid()
+        deferred = self.flickr.people_getInfo(user_id=user_id)
+        deferred.addCallback(getinfo_cb, user_id)
+        deferred.addErrback(log.err)
 
     def on_field_changed(self, widget, column):
         """Callback when the entry fields are changed."""
@@ -499,8 +514,7 @@ class Postr(UniqueApp):
         for gfile in self.temporary_files:
             gfile.delete(None)
 
-        import twisted.internet.reactor
-        twisted.internet.reactor.stop()
+        reactor.stop()
 
     def on_save_session_activate(self, widget):
         """Callback from File->Save session."""
@@ -558,7 +572,9 @@ class Postr(UniqueApp):
     def on_switch_activate(self, menuitem):
         """Callback from File->Switch User."""
         self.flickr.clear_cached()
-        self.flickr.authenticate_1().addCallbacks(self.auth_open_url, self.twisted_error)
+        deferred = self.flickr.authenticate_1()
+        deferred.addCallback(self.auth_open_url)
+        deferred.addErrback(self.twisted_error)
 
     def on_upload_activate(self, menuitem):
         """Callback from File->Upload."""
@@ -1103,7 +1119,8 @@ class Postr(UniqueApp):
                                        progress_tracker=self.upload_progress_tracker)
             else:
                 print "No filename or pixbuf stored"
-        except Gio.Error, (error):
+        except GLib.GError, e:
+            print e
             # save the iterator and continue uploading process
             self.list_failed_it.append(it)
             self.current_upload_it = None
@@ -1112,29 +1129,34 @@ class Postr(UniqueApp):
 
         if set_id:
             d.addCallback(self.add_to_set, set_id)
+            d.addErrback(log.err)
         else:
             self.upload_progress_tracker.complete_extra_step(EXTRA_STEP_SET_ID)
         if groups:
             d.addCallback(self.add_to_groups, groups)
+            d.addErrback(log.err)
         else:
             self.upload_progress_tracker.complete_extra_step(EXTRA_STEP_GROUPS)
         if license is not None: # 0 is a valid license.
             d.addCallback(self.set_license, license)
+            d.addErrback(log.err)
         else:
             self.upload_progress_tracker.complete_extra_step(EXTRA_STEP_LICENSE)
         # creating a new photoset has implications on subsequent uploads,
         # so this has to finish before starting the next upload
         if new_photoset_name:
             d.addCallback(self.create_photoset_then_continue, new_photoset_name)
+            d.addErrback(log.err)
         else:
             d.addCallbacks(self.upload, self.upload_error)
+            d.addErrback(self.log.err)
             self.upload_progress_tracker.complete_extra_step(EXTRA_STEP_NEW_SET)
 
     def create_photoset_then_continue(self, rsp, photoset_name):
         photo_id = rsp.find("photoid").text
-        create_photoset = self.flickr.photosets_create(primary_photo_id=photo_id, title=photoset_name)
-        create_photoset.addCallback(self._process_photoset_creation, photoset_name)
-        create_photoset.addErrback(self.upload_error)
+        deferred = self.flickr.photosets_create(primary_photo_id=photo_id, title=photoset_name)
+        deferred.addCallback(self._process_photoset_creation, photoset_name)
+        deferred.addErrback(self.upload_error)
         return rsp
 
     def _process_photoset_creation(self, create_rsp, photoset_name):
diff --git a/src/util.py b/src/util.py
index 5974ae1..eed535a 100644
--- a/src/util.py
+++ b/src/util.py
@@ -18,6 +18,9 @@
 import os
 from gi.repository import Gtk, GdkPixbuf
 import bsddb3
+from twisted.web.client import getPage
+from twisted.internet import defer
+from twisted.python import log
 
 def greek(size):
     """Take a quantity (like 1873627) and display it in a human-readable rounded
@@ -72,7 +75,6 @@ __buddy_cache = None
 def get_buddyicon(flickr, data, size=48):
     """Lookup the buddyicon from the data in @data using @flickr and resize it
     to @size pixels."""
-    from twisted.web.client import getPage
 
     global __buddy_cache
     if __buddy_cache is None:
@@ -104,11 +106,12 @@ def get_buddyicon(flickr, data, size=48):
         url = "http://www.flickr.com/images/buddyicon.jpg";
 
     if __buddy_cache.has_key(url):
-        from twisted.internet import defer
-        return defer.succeed(load_thumb(__buddy_cache[url], size))
+        return defer.execute(load_thumb, __buddy_cache[url], size)
     else:
-        return getPage(url).addCallback(got_data, url, size)
-
+        deferred = getPage(url)
+        deferred.addCallback(got_data, url, size)
+        deferred.addErrback(log.err)
+        return deferred
 
 def get_cache_path():
     """Return the location of the XDG cache directory."""



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