From: Fredrik Wendt Date: Sat, 5 Jun 2010 21:30:25 +0000 (+0200) Subject: rewrite to match new service API and enable some testing X-Git-Tag: 0.8.0~70 X-Git-Url: http://vcs.maemo.org/git/?p=hermes;a=commitdiff_plain;h=d0585f45dfd5f88cd1690ea3a3a595d19c94f24c rewrite to match new service API and enable some testing Signed-off-by: Fredrik Wendt --- diff --git a/package/src/org/maemo/hermes/engine/facebook/provider.py b/package/src/org/maemo/hermes/engine/facebook/provider.py index 44661f3..56e3f17 100644 --- a/package/src/org/maemo/hermes/engine/facebook/provider.py +++ b/package/src/org/maemo/hermes/engine/facebook/provider.py @@ -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 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'])) diff --git a/package/src/org/maemo/hermes/engine/facebook/service.py b/package/src/org/maemo/hermes/engine/facebook/service.py index 150b345..f917131 100644 --- a/package/src/org/maemo/hermes/engine/facebook/service.py +++ b/package/src/org/maemo/hermes/engine/facebook/service.py @@ -1,156 +1,118 @@ -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 2010. + Copyright (c) Fredrik Wendt 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) diff --git a/package/src/org/maemo/hermes/engine/service.py b/package/src/org/maemo/hermes/engine/service.py index eb5987b..f30899c 100644 --- a/package/src/org/maemo/hermes/engine/service.py +++ b/package/src/org/maemo/hermes/engine/service.py @@ -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 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())