rewrite to match new service API and enable some testing
authorFredrik Wendt <fredrik@wendt.se>
Sat, 5 Jun 2010 21:30:25 +0000 (23:30 +0200)
committerFredrik Wendt <fredrik@wendt.se>
Sat, 5 Jun 2010 21:30:25 +0000 (23:30 +0200)
Signed-off-by: Fredrik Wendt <fredrik@wendt.se>

package/src/org/maemo/hermes/engine/facebook/provider.py
package/src/org/maemo/hermes/engine/facebook/service.py
package/src/org/maemo/hermes/engine/service.py

index 44661f3..56e3f17 100644 (file)
@@ -1,9 +1,15 @@
+from facebook import Facebook, FacebookError
+import gnome.gconf
 import org.maemo.hermes.engine.provider
 import org.maemo.hermes.engine.facebook.service
 
 class Provider(org.maemo.hermes.engine.provider.Provider):
     """Facebook provider for Hermes. 
 
+       This requires two gconf paths to contain Facebook application keys:
+           /apps/maemo/hermes/key_app
+           /apps/maemo/hermes/key_secret
+       
        Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
        Released under the Artistic Licence."""
 
@@ -28,16 +34,65 @@ class Provider(org.maemo.hermes.engine.provider.Provider):
         """Open the preferences for this provider as a child of the 'parent' widget."""
 
         print "Err, open preferences. OK. Err, right. Hmm."
+        
+        
+    # -----------------------------------------------------------------------
+    def prepare_service(self):
+        # -- Check the environment is going to work...
+        #
+        if (self._gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
+            raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
+
+        key_app    = self._gc.get_string('/apps/maemo/hermes/key_app')
+        key_secret = self._gc.get_string('/apps/maemo/hermes/key_secret')
+        if key_app is None or key_secret is None:
+            raise Exception('No Facebook application keys found. Installation error.')
+        
+        self.fb = Facebook(key_app, key_secret)
+        self.fb.desktop = True
+
+        if self.fb.session_key is None:
+            self.fb.session_key = self._gc.get_string('/apps/maemo/hermes/session_key')
+            self.fb.secret = self._gc.get_string('/apps/maemo/hermes/secret_key')
+            self.fb.uid = self._gc.get_string('/apps/maemo/hermes/uid')
+        
+        # Check the available session is still valid...
+        while True:
+            try:
+                if self.fb.users.getLoggedInUser() and self.fb.session_key:
+                    break
+            except FacebookError:
+                pass
+            self._do_fb_login()
+
 
     
     # -----------------------------------------------------------------------
     def service(self, gui_callback):
-        """Return the service backend. This must be a class which implements the
-           following methods:
-               * get_friends
-               * process_contact
-               * finalise
+        return org.maemo.hermes.engine.facebook.service.Service(self.fb)
+
+
+    # -----------------------------------------------------------------------
+    def _do_fb_login(self):
+        """Perform authentication against Facebook and store the result in gconf
+             for later use. Uses the 'need_auth' and 'block_for_auth' methods on
+             the callback class. The former allows a message to warn the user
+             about what is about to happen to be shown; the second is to wait
+             for the user to confirm they have logged in."""
+        self.fb.session_key = None
+        self.fb.secret = None
+        self.fb.uid = None
         
-           See Service for more details."""
-           
-        return org.maemo.hermes.engine.facebook.service.Service()
+        if self._gui:
+            self._gui.need_auth()
+            
+        self.fb.auth.createToken()
+        self.fb.login()
+        
+        if self._gui:
+            self._gui.block_for_auth()
+          
+        session = self.fb.auth.getSession()
+        self._gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
+        self._gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
+        self._gc.set_string('/apps/maemo/hermes/uid', str(session['uid']))
index 150b345..f917131 100644 (file)
-import gnome.gconf
 import org.maemo.hermes.engine.service
 
-from facebook import Facebook
 from org.maemo.hermes.engine.names import canonical
 
 class Service(org.maemo.hermes.engine.service.Service):
     """Facebook backend for Hermes.
                 
-       This requires two gconf paths to contain Facebook application keys:
-           /apps/maemo/hermes/key_app
-           /apps/maemo/hermes/key_secret
-       
        Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
+       Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
        Released under the Artistic Licence."""
        
+    attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
        
     # -----------------------------------------------------------------------
-    def __init__(self, autocreate = False, gui_callback = None):
+    def __init__(self, facebook, autocreate = False, gui_callback = None):
         """Initialise the Facebook service, finding Facebook API keys in gconf and
            having a gui_callback available."""
         
-        self._gc  = gnome.gconf.client_get_default()
         self._gui = gui_callback
         self._autocreate = autocreate
+        self.fb = facebook
         
-        # -- Check the environment is going to work...
-        #
-        if (self._gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
-            raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
-
-        key_app    = self._gc.get_string('/apps/maemo/hermes/key_app')
-        key_secret = self._gc.get_string('/apps/maemo/hermes/key_secret')
-        if key_app is None or key_secret is None:
-            raise Exception('No Facebook application keys found. Installation error.')
-
-        self.fb = Facebook(key_app, key_secret)
-        self.fb.desktop = True
-        
-        self._friends = None
+        self._friends_by_id = {}
         self._friends_by_url = {}
-        self._should_create = set()
+        self._friends_by_contact = {}
+        self._contacts_by_friend = {}
+        self._friends_without_contact = set()
+        self._known_urls = set()
 
 
     # -----------------------------------------------------------------------
-    def _do_fb_login(self):
-        """Perform authentication against Facebook and store the result in gconf
-             for later use. Uses the 'need_auth' and 'block_for_auth' methods on
-             the callback class. The former allows a message to warn the user
-             about what is about to happen to be shown; the second is to wait
-             for the user to confirm they have logged in."""
-        self.fb.session_key = None
-        self.fb.secret = None
-        self.fb.uid = None
-        
-        if self._gui:
-            self._gui.need_auth()
-            
-        self.fb.auth.createToken()
-        self.fb.login()
-        
-        if self._gui:
-            self._gui.block_for_auth()
-          
-        session = self.fb.auth.getSession()
-        self._gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
-        self._gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
-        self._gc.set_string('/apps/maemo/hermes/uid', str(session['uid']))
-
-   
+    def get_name(self):
+        return "Facebook"
+    
+    
     # -----------------------------------------------------------------------
     def get_friends(self):
-        """Return a list of friends from this service, or 'None' if manual mapping
-           is not supported."""
-           
-        if self._friends:
-            return self._friends.values()
-         
-        if self.fb.session_key is None:
-            self.fb.session_key = self._gc.get_string('/apps/maemo/hermes/session_key')
-            self.fb.secret = self._gc.get_string('/apps/maemo/hermes/secret_key')
-            self.fb.uid = self._gc.get_string('/apps/maemo/hermes/uid')
+        return self._contacts_by_friend.keys()
+    
+    
+    def get_contacts_with_match(self):
+        return self._friends_by_contact
+    
+    def get_unmatched_friends(self):
+        return self._friends_by_id.values()
+
+    # -----------------------------------------------------------------------
+    def pre_process_contact(self, contact):
+        """Registers URLs of all previous mappings, and makes sure that any Friends with those
+           URLs don't get match by name."""
+        for url in contact.get_urls():
+            self._known_urls.add(url)
+    
+    
+    # -----------------------------------------------------------------------
+    def process_friends(self):
         
-        # Check the available session is still valid...
-        while True:
-            try:
-                if self.fb.users.getLoggedInUser() and self.fb.session_key:
-                    break
-            except FacebookError:
-                pass
-            self._do_fb_login()
-            
-        self._friends = {}
-        attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
-        for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
-            print "#"
-            friend['key']= canonical(friend['name'])
-            self._friends[friend['key']] = friend
+        def if_defined(data, key, callback):
+            if key in data and data[key]:
+                callback(data[key])
         
-            if 'profile_url' not in friend:
-                friend['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(friend ['uid'])
+        friends_data = self._get_friends_data()
+        for data in friends_data:
+            key = canonical(data['name']) # FIXME: deal with name collision
+            friend = self._create_friend(data['name'])
         
-            self._friends_by_url[friend['profile_url']] = friend
-            friend['pic']  = friend[attrs[2]]
-            friend['account'] = 'Facebook'
+            if 'profile_url' not in data:
+                data['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(data['uid'])
         
-            if 'website' in friend and friend['website']:
-                friend['homepage'] = friend['website']
-                
-            if 'birthday_date' in friend and friend['birthday_date']:
-                self._should_create.add(friend['profile_url'])
-                
-        return self._friends.values()
+            if_defined(data, 'website', friend.add_url)
+            if_defined(data, 'profile_url', friend.add_url)
+            if_defined(data, 'birthday_date', friend.set_birthday_date)
 
-    
-    # -----------------------------------------------------------------------
-    def process_contact(self, contact, overwrite = False, friend = None):
-        """Called for each contact in the address book. Any friends linked to
-           from the contact should have their matching updated. The backend should 
-           enrich the contact with any meta-data it can; and return 'True' if any
-           information was updated. If 'friend' is provided, the information should
-           be retrieved from friend. This will be one of the entries from 
-           'get_friends' when manual mapping is being done."""
-           
-        if not self._friends:
-            self.get_friends()
+            if_defined(data, 'pic_big', friend.set_photo_url)
             
+            if friend.has_birthday_date(): # FIXME: remove this, either you want to add your contacts or not? 
+                self._friends_without_contact.add(friend)
+            url = data['profile_url']
+            self._friends_by_url[url] = friend
+            
+            if url not in self._known_urls:
+                self._friends_by_id[key] = friend
+
+    # -----------------------------------------------------------------------
+    def process_contact(self, contact):
+        if self._friends_by_contact.has_key(contact):
+            return
+        
         matched_friend = None
+        # we might get a hit if the friend has setup a URL with another service,
+        # such as putting the id link to Facebook on the Twitter account's profile
         for url in contact.get_urls():
             if url in self._friends_by_url:
                 matched_friend = self._friends_by_url[url]
+                self._register_match(contact, matched_friend)
+                print contact.get_name(), " -> match by url -> ", matched_friend
                 break
 
         if not matched_friend:
             for id in contact.get_identifiers():
-                if id in self._friends:
-                    matched_friend = self._friends[id]
+                if id in self._friends_by_id:
+                    matched_friend = self._friends_by_id.pop(id)
+                    self._register_match(contact, matched_friend)
+                    print contact.get_name(), " -> match by name -> ", matched_friend
                     break
-                
-        if matched_friend:
-            print contact.get_name(), " -> ", matched_friend['profile_url']
-            self._should_create.discard(matched_friend['profile_url'])
-        else:
-            print contact.get_name(), " no match to Facebook with ", contact.get_identifiers()
-            
-    
     
+
     # -----------------------------------------------------------------------
-    def finalise(self, updated, overwrite = False):
-        """Once all contacts have been processed, allows for any tidy-up/additional
-           enrichment. If any contacts are updated at this stage, 'updated' should
-           be added to."""
+    def _register_match(self, contact, friend):
+        self._friends_without_contact.discard(friend)
+        self._friends_by_contact[contact] = friend
+        self._contacts_by_friend[friend] = contact
 
-        if self._autocreate:
-            for url in self._should_create:
-                print "Need to autocreate:", self._friends_by_url[url]
+    # -----------------------------------------------------------------------
+    def _get_friends_data(self):
+        """Returns a list of dicts, where each dict represents a friend/contact"""
+        
+        return self.fb.users.getInfo(self.fb.friends.get(), Service.attrs)
index eb5987b..f30899c 100644 (file)
@@ -1,35 +1,67 @@
+from friend import Friend
+
 class Service:
     """The notional `Service' for a provider. This is responsible for communicating
        with the backend service and enhancing contacts.
        
        Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
        Released under the Artistic Licence."""
+       
+    def __init__(self):
+        """Should make initial calls to the service and retrieve a list of friends."""
 
 
     # -----------------------------------------------------------------------
-    def get_friends(self):
-        """Return a list of friends from this service, or 'None' if manual mapping
-           is not supported."""
+    def get_name(self):
+        """Should return the same name as the provider class - debugging only - REMOVE/FIXME"""
+        
+        return None
+
+    
+    # -----------------------------------------------------------------------
+    def pre_process_contact(self, contact):
+        """If the contact have an URL (or similar) that proves an existing matching
+           to a friend on this service, then this should be remembered to avoid
+           name collision/mapping the same friend to other contacts with the same 
+           name."""
          
         return None
 
     
+    def process_friends(self):
+        """Called once to signal that it's time to get all friends' data."""
+        
+        pass
+    
+    
     # -----------------------------------------------------------------------
-    def process_contact(self, contact, overwrite = False, friend = None):
+    def process_contact(self, contact):
         """Called for each contact in the address book. Any friends linked to
-           from the contact should have their matching updated. The backend should 
-           enrich the contact with any meta-data it can; and return 'True' if any
-           information was updated. If 'friend' is provided, the information should
-           be retrieved from friend. This will be one of the entries from 
-           'get_friends' when manual mapping is being done."""
+           from the contact should have their matching updated. The back end should 
+           enrich the friend passed with any meta-data it can."""
            
         pass
     
     
     # -----------------------------------------------------------------------
-    def finalise(self, updated, overwrite = False):
+    def get_unmatched_friends(self):
+        """Return a list of friends not matched to a contact, or 'None' if manual mapping
+           is not supported."""
+         
+        return None
+
+
+    # -----------------------------------------------------------------------
+    def finalise(self, updated, overwrite=False):
         """Once all contacts have been processed, allows for any tidy-up/additional
            enrichment. If any contacts are updated at this stage, 'updated' should
            be added to."""
            
         pass
+
+
+    # -----------------------------------------------------------------------
+    def _create_friend(self, name):
+        """Used to create a Friend object for a contact"""
+        
+        return Friend(name, self.get_name())