diff --git a/services/presence2/buddy.py b/services/presence2/buddy.py index 622fcf98..35bb583d 100644 --- a/services/presence2/buddy.py +++ b/services/presence2/buddy.py @@ -32,7 +32,7 @@ class Buddy(dbus.service.Object): """Represents another person on the network and keeps track of the activities and resources they make available for sharing.""" - def __init__(self, bus_name, object_id, icon_cache, handle=None): + def __init__(self, bus_name, object_id, handle=None): if not bus_name: raise ValueError("DBus bus name must be valid") if not object_id or not isinstance(object_id, int): @@ -46,10 +46,9 @@ class Buddy(dbus.service.Object): self._activities = {} # Activity ID -> Activity - self._icon_cache = icon_cache - self.handles = {} # tp client -> handle + self._icon = None self._nick_name = None self._color = None self._key = None @@ -149,7 +148,7 @@ class Buddy(dbus.service.Object): return None return self._activities[self._current_activity] - def _set_icon(self, icon): + def set_icon(self, icon): """Can only set icon for other buddies. The Owner takes care of setting it's own icon.""" if icon != self._icon: @@ -181,8 +180,8 @@ class Buddy(dbus.service.Object): class Owner(Buddy): """Class representing the owner of the machine. This is the client portion of the Owner, paired with the server portion in Owner.py.""" - def __init__(self, ps, bus_name, object_id, icon_cache): - Buddy.__init__(self, bus_name, object_id, icon_cache) + def __init__(self, ps, bus_name, object_id): + Buddy.__init__(self, bus_name, object_id) self._ps = ps self._nick_name = profile.get_nick_name() diff --git a/services/presence2/buddyiconcache.py b/services/presence2/buddyiconcache.py index 7eb12eb8..b11ddd5b 100644 --- a/services/presence2/buddyiconcache.py +++ b/services/presence2/buddyiconcache.py @@ -1,4 +1,5 @@ # Copyright (C) 2007, Red Hat, Inc. +# Copyright (C) 2007, Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,64 +15,64 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import os, time, md5 from sugar import env from sugar import util +import os.path +import cPickle + class BuddyIconCache(object): - """Caches icons on disk and finds them based on md5 hash.""" + """Caches icons on disk and finds them based on the jid of their owners.""" def __init__(self): ppath = env.get_profile_path() - self._cachepath = os.path.join(ppath, "cache", "buddy-icons") + self._cachepath = os.path.join(ppath, "cache", "buddy-icons", "cache") + if not os.path.exists(self._cachepath): - os.makedirs(self._cachepath) + self._cache = {} + else: + self._load_cache() - self._cache = {} - - # Read all cached icons and their sums - for fname in os.listdir(self._cachepath): - m = md5.new() - data = self._get_icon_data(fname) - if len(data) == 0: - continue - m.update(data) - printable_hash = util.printable_hash(m.digest()) - self._cache[printable_hash] = fname - del m - - def _get_icon_data(self, fname): - fd = open(os.path.join(self._cachepath, fname), "r") - data = fd.read() - fd.close() - del fd - return data - - def get_icon(self, printable_hash): - if not isinstance(printable_hash, unicode): - raise RuntimeError("printable_hash must be a unicode string.") + def _load_cache(self): try: - fname = self._cache[printable_hash] - return self._get_icon_data(fname) - except KeyError: - pass + self._cache = cPickle.load(open(self._cachepath, "r")) + except: + self._cache = {} + + + def _save_cache(self): + out = open(self._cachepath, "w") + cPickle.dump(self._cache, out, protocol=2) + + def get_icon(self, jid, token): + hit = self._cache.get(jid) + + if hit: + t, icon = hit[0], hit[1] + if t == token: + return icon + return None - def add_icon(self, icon_data): - if len(icon_data) == 0: - return + def store_icon(self, jid, token, data): + self._cache[jid] = (token, data) + self._save_cache() - m = md5.new() - m.update(icon_data) - printable_hash = util.printable_hash(m.digest()) - if self._cache.has_key(printable_hash): - del m - return +if __name__ == "__main__": + my_cache = BuddyIconCache() + + # look for the icon in the cache + icon = my_cache.get_icon("test@olpc.collabora.co.uk", "aaaa") + print icon - # Write the icon to disk and add an entry to our cache for it - m.update(time.asctime()) - fname = util.printable_hash(m.digest()) - fd = open(os.path.join(self._cachepath, fname), "w") - fd.write(icon_data) - fd.close() - self._cache[printable_hash] = fname - del m + my_cache.store_icon("test@olpc.collabora.co.uk", "aaaa", "icon1") + + # now we're sure that the icon is in the cache + icon = my_cache.get_icon("test@olpc.collabora.co.uk", "aaaa") + print icon + + # new icon + my_cache.store_icon("test@olpc.collabora.co.uk", "bbbb", "icon2") + + # the icon in the cache is not valid now + icon = my_cache.get_icon("test@olpc.collabora.co.uk", "aaaa") + print icon diff --git a/services/presence2/presenceservice.py b/services/presence2/presenceservice.py index fa115a80..32afcf76 100644 --- a/services/presence2/presenceservice.py +++ b/services/presence2/presenceservice.py @@ -26,8 +26,6 @@ from linklocal_plugin import LinkLocalPlugin from buddy import Buddy, Owner from activity import Activity -import buddyiconcache - _PRESENCE_SERVICE = "org.laptop.Sugar.Presence" _PRESENCE_INTERFACE = "org.laptop.Sugar.Presence" @@ -48,14 +46,12 @@ class PresenceService(dbus.service.Object): self._handles = {} # tp client -> (handle -> Buddy) self._activities = {} # activity id -> Activity - self._icon_cache = buddyiconcache.BuddyIconCache() - bus = dbus.SessionBus() self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=bus) # Create the Owner object objid = self._get_next_object_id() - self._owner = Owner(self, self._bus_name, objid, self._icon_cache) + self._owner = Owner(self, self._bus_name, objid) self._buddies[self._owner.get_key()] = self._owner self._registry = ManagerRegistry() @@ -68,6 +64,7 @@ class PresenceService(dbus.service.Object): self._server_plugin.connect('status', self._server_status_cb) self._server_plugin.connect('contact-online', self._contact_online) self._server_plugin.connect('contact-offline', self._contact_offline) + self._server_plugin.connect('avatar-updated', self._avatar_updated) self._server_plugin.start() # Set up the link local connection @@ -86,9 +83,9 @@ class PresenceService(dbus.service.Object): if not buddy: # we don't know yet this buddy objid = self._get_next_object_id() - buddy = Buddy(self._bus_name, objid, self._icon_cache, handle=handle) + buddy = Buddy(self._bus_name, objid, handle=handle) buddy.set_key(key) - print "create buddy" + print "create buddy", key self._buddies[key] = buddy new_buddy = True @@ -120,6 +117,12 @@ class PresenceService(dbus.service.Object): self._next_object_id = self._next_object_id + 1 return self._next_object_id + def _avatar_updated(self, tp, handle, avatar): + buddy = self._handles[tp].get(handle) + + if buddy: + buddy.set_icon(avatar) + @dbus.service.signal(_PRESENCE_INTERFACE, signature="o") def ActivityAppeared(self, activity): pass diff --git a/services/presence2/server_plugin.py b/services/presence2/server_plugin.py index 06d15873..b65fdde6 100644 --- a/services/presence2/server_plugin.py +++ b/services/presence2/server_plugin.py @@ -18,6 +18,7 @@ import gobject from sugar import profile from sugar import util +from buddyiconcache import BuddyIconCache import logging from telepathy.client import ConnectionManager, ManagerRegistry, Connection, Channel @@ -38,14 +39,18 @@ class ServerPlugin(gobject.GObject): 'contact-offline': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_INT, gobject.TYPE_INT])) + ([gobject.TYPE_INT, gobject.TYPE_INT])), + 'avatar-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) } def __init__(self, registry): gobject.GObject.__init__(self) + self._icon_cache = BuddyIconCache() + self._registry = registry - self._online_contacts = set() + self._online_contacts = set() # handles of online contacts self._account = self._get_account_info() self._ever_connected = False @@ -154,8 +159,9 @@ class ServerPlugin(gobject.GObject): # hack self._conn._valid_interfaces.add(CONN_INTERFACE_AVATARS) + self._conn[CONN_INTERFACE_AVATARS].connect_to_signal('AvatarUpdated', self._avatar_updated_cb) #if CONN_INTERFACE_AVATARS in self._conn: - # #tokens = self._conn[CONN_INTERFACE_AVATARS].RequestAvatarTokens(subscribe_handles) + # tokens = self._conn[CONN_INTERFACE_AVATARS].RequestAvatarTokens(subscribe_handles) # #for handle, token in zip(subscribe_handles, tokens): # for handle in subscribe_handles: @@ -212,15 +218,15 @@ class ServerPlugin(gobject.GObject): self._conn[CONN_INTERFACE].Disconnect() def _contact_go_offline(self, handle): - name = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] - print name, "offline" + jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] + print jid, "offline" self._online_contacts.remove(handle) self.emit("contact-offline", handle) def _contact_go_online(self, handle): - name = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] - print name, "online" + jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] + print jid, "online" # TODO: use the OLPC interface to get the key key = handle @@ -241,3 +247,15 @@ class ServerPlugin(gobject.GObject): elif online and status in ["offline", "invisible"]: self._contact_go_offline(handle) + def _avatar_updated_cb(self, handle, new_avatar_token): + jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] + + icon = self._icon_cache.get_icon(jid, new_avatar_token) + + if not icon: + # cache miss + avatar, mime_type = self._conn[CONN_INTERFACE_AVATARS].RequestAvatar(handle) + icon = ''.join(map(chr, avatar)) + self._icon_cache.store_icon(jid, new_avatar_token, icon) + + self.emit("avatar-updated", handle, icon)