diff --git a/services/presence2/Makefile.am b/services/presence2/Makefile.am index b0942db1..fbd3125c 100644 --- a/services/presence2/Makefile.am +++ b/services/presence2/Makefile.am @@ -4,7 +4,9 @@ sugar_PYTHON = \ activity.py \ buddy.py \ buddyiconcache.py \ - presenceservice.py + linklocal_plugin.py \ + presenceservice.py \ + server_plugin.py bin_SCRIPTS = sugar-presence-service2 diff --git a/services/presence2/buddy.py b/services/presence2/buddy.py index 63f6da9f..e010d19c 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): + def __init__(self, bus_name, object_id, icon_cache, handle=None): if not bus_name: raise ValueError("DBus bus name must be valid") if not object_id or not isinstance(object_id, int): @@ -48,6 +48,8 @@ class Buddy(dbus.service.Object): self._icon_cache = icon_cache + self._handle = handle + self._nick_name = None self._color = None self._key = None diff --git a/services/presence2/presenceservice.py b/services/presence2/presenceservice.py index 49a4dc6f..b5626c41 100644 --- a/services/presence2/presenceservice.py +++ b/services/presence2/presenceservice.py @@ -21,11 +21,12 @@ from telepathy.interfaces import (CONN_MGR_INTERFACE, CONN_INTERFACE) from telepathy.constants import (CONNECTION_STATUS_CONNECTING, CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, CONNECTION_HANDLE_TYPE_CONTACT) -import telepathyclient +from server_plugin import ServerPlugin +from linklocal_plugin import LinkLocalPlugin + from buddy import Buddy, Owner from activity import Activity import buddyiconcache -from sugar import profile _PRESENCE_SERVICE = "org.laptop.Sugar.Presence" @@ -60,50 +61,23 @@ class PresenceService(dbus.service.Object): self._registry = ManagerRegistry() self._registry.LoadManagers() - self._server_client = self._connect_to_server() - self._handles[self._server_client] = {} + # Set up the server connection + self._server_plugin = ServerPlugin(self._registry) + self._handles[self._server_plugin] = {} - # Telepathy link local connection - self._ll_client = None + 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.start() - self._server_client.connect('contact-online', self._contact_online) - self._server_client.connect('contact-offline', self._contact_offline) - self._server_client.run() + # Set up the link local connection + self._ll_plugin = LinkLocalPlugin(self._registry) + self._handles[self._ll_plugin] = {} dbus.service.Object.__init__(self, self._bus_name, _PRESENCE_PATH) - def _connect_to_server(self): - protocol = 'jabber' - account = { - 'account': 'blah@collabora.co.uk', - 'password': 'learn', - 'server': 'light.bluelinux.co.uk' - } - - mgr = self._registry.GetManager('gabble') - conn = None - - # Search existing connections, if any, that we might be able to use - connections = Connection.get_connections() - for item in connections: - if item[CONN_INTERFACE].GetProtocol() != protocol: - continue - if not item.object_path.startswith("/org/freedesktop/Telepathy/Connection/gabble/jabber/"): - continue - if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED: - self_name = account['account'] - test_handle = item[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_CONTACT, [self_name])[0] - if item[CONN_INTERFACE].GetSelfHandle() != test_handle: - continue - conn = item - - if not conn: - # Create a new connection - conn_bus_name, conn_object_path = \ - mgr[CONN_MGR_INTERFACE].RequestConnection(protocol, account) - conn = Connection(conn_bus_name, conn_object_path) - - return telepathyclient.TelepathyClient(conn) + def _server_status_cb(self, plugin, status): + pass def _contact_online(self, tp, handle, key): buddy = self._buddies.get(key) @@ -111,7 +85,7 @@ 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) + buddy = Buddy(self._bus_name, objid, self._icon_cache, handle=handle) buddy.set_key(key) print "create buddy" self._buddies[key] = buddy diff --git a/services/presence2/server_plugin.py b/services/presence2/server_plugin.py new file mode 100644 index 00000000..2a977f74 --- /dev/null +++ b/services/presence2/server_plugin.py @@ -0,0 +1,225 @@ +# 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +from sugar import profile +from sugar import util +import logging + +from telepathy.client import ConnectionManager, ManagerRegistry, Connection, Channel +from telepathy.interfaces import ( + CONN_MGR_INTERFACE, CONN_INTERFACE, CHANNEL_TYPE_CONTACT_LIST, CHANNEL_INTERFACE_GROUP, CONN_INTERFACE_ALIASING, + CONN_INTERFACE_AVATARS, CONN_INTERFACE_PRESENCE) +from telepathy.constants import ( + CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT, + CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, CONNECTION_STATUS_CONNECTING, + CONNECTION_HANDLE_TYPE_LIST, CONNECTION_HANDLE_TYPE_CONTACT) + + +class ServerPlugin(gobject.GObject): + __gsignals__ = { + 'contact-online': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), + 'contact-offline': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_INT])) + } + + def __init__(self, registry): + gobject.GObject.__init__(self) + + self._registry = registry + self._online_contacts = set() + self._account = self._get_account_info() + + self._conn = self._init_connection() + + def _get_account_info(self): + account_info = {'server': 'olpc.collabora.co.uk'} + + pubkey = profile.get_pubkey() + khash = util.printable_hash(util._sha_data(pubkey)) + account_info['account'] = "%s@%s" % (khash, account_info['server']) + + account_info['password'] = profile.get_private_key_hash() + return account_info + + def _get_connection(self): + protocol = 'jabber' + + mgr = self._registry.GetManager('gabble') + + # Search existing connections, if any, that we might be able to use + connections = Connection.get_connections() + conn = None + for item in connections: + if not item.object_path.startswith("/org/freedesktop/Telepathy/Connection/gabble/jabber/"): + continue + if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_DISCONNECTED: + item[CONN_INTERFACE].Disconnect() + continue + if item[CONN_INTERFACE].GetProtocol() != protocol: + continue + if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED: + self_name = self._account['account'] + test_handle = item[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_CONTACT, [self_name])[0] + if item[CONN_INTERFACE].GetSelfHandle() != test_handle: + continue + conn = item + break + + if not conn: + # Create a new connection + conn_bus_name, conn_object_path = \ + mgr[CONN_MGR_INTERFACE].RequestConnection(protocol, + self._account) + conn = Connection(conn_bus_name, conn_object_path) + + conn[CONN_INTERFACE].connect_to_signal('StatusChanged', self._status_changed_cb) + + # hack + conn._valid_interfaces.add(CONN_INTERFACE_PRESENCE) + conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate', + self._presence_update_cb) + + return conn + + def _request_list_channel(self, name): + handle = self._conn[CONN_INTERFACE].RequestHandles( + CONNECTION_HANDLE_TYPE_LIST, [name])[0] + chan_path = self._conn[CONN_INTERFACE].RequestChannel( + CHANNEL_TYPE_CONTACT_LIST, CONNECTION_HANDLE_TYPE_LIST, + handle, True) + channel = Channel(self._conn._dbus_object._named_service, chan_path) + # hack + channel._valid_interfaces.add(CHANNEL_INTERFACE_GROUP) + return channel + + def _connected_cb(self): + # the group of contacts who may receive your presence + publish = self._request_list_channel('publish') + publish_handles, local_pending, remote_pending = publish[CHANNEL_INTERFACE_GROUP].GetAllMembers() + + # the group of contacts for whom you wish to receive presence + subscribe = self._request_list_channel('subscribe') + subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers() + + if local_pending: + # accept pending subscriptions + #print 'pending: %r' % local_pending + publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '') + + not_subscribed = list(set(publish_handles) - set(subscribe_handles)) + self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() + self._online_contacts.add(self_handle) + + for handle in not_subscribed: + # request subscriptions from people subscribed to us if we're not subscribed to them + subscribe[CHANNEL_INTERFACE_GROUP].AddMembers([self_handle], '') + + # hack + self._conn._valid_interfaces.add(CONN_INTERFACE_ALIASING) + + if CONN_INTERFACE_ALIASING in self._conn: + aliases = self._conn[CONN_INTERFACE_ALIASING].RequestAliases(subscribe_handles) + else: + aliases = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, subscribe_handles) + + #for handle, alias in zip(subscribe_handles, aliases): + # print alias + # self.buddies[handle].alias = alias + + # hack + self._conn._valid_interfaces.add(CONN_INTERFACE_AVATARS) + + #if CONN_INTERFACE_AVATARS in self._conn: + # #tokens = self._conn[CONN_INTERFACE_AVATARS].RequestAvatarTokens(subscribe_handles) + + # #for handle, token in zip(subscribe_handles, tokens): + # for handle in subscribe_handles: + # avatar, mime_type = self._conn[CONN_INTERFACE_AVATARS].RequestAvatar(handle) + # self.buddies[handle].avatar = ''.join(map(chr, avatar)) + + # import gtk + # window = gtk.Window() + # window.set_title(self.buddies[handle].alias) + # loader = gtk.gdk.PixbufLoader() + # loader.write(self.buddies[handle].avatar) + # loader.close() + # image = gtk.Image() + # image.set_from_pixbuf(loader.get_pixbuf()) + # window.add(image) + # window.show_all() + + def _status_changed_cb(self, state, reason): + if state == CONNECTION_STATUS_CONNECTING: + print 'connecting: %r' % reason + elif state == CONNECTION_STATUS_CONNECTED: + print 'connected: %r' % reason + self.emit('status', state) + self._connected_cb() + elif state == CONNECTION_STATUS_DISCONNECTED: + print 'disconnected: %r' % reason + self.emit('status', state, int(reason)) + + def start(self): + # If the connection is already connected query initial contacts + conn_status = self._conn[CONN_INTERFACE].GetStatus() + if conn_status == CONNECTION_STATUS_CONNECTED: + self._connected_cb() + subscribe = self._request_list_channel('subscribe') + subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers() + self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles) + elif conn_status == CONNECTION_STATUS_CONNECTING: + pass + else: + self._conn[CONN_INTERFACE].Connect() + + def disconnect(self): + 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" + + 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" + + # TODO: use the OLPC interface to get the key + key = handle + + self._online_contacts.add(handle) + self.emit("contact-online", handle, key) + + def _presence_update_cb(self, presence): + for handle in presence: + timestamp, statuses = presence[handle] + + name = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] + online = handle in self._online_contacts + + for status, params in statuses.items(): + if not online and status in ["available", "away", "brb", "busy", "dnd", "xa"]: + self._contact_go_online(handle) + elif online and status in ["offline", "invisible"]: + self._contact_go_offline(handle) + diff --git a/services/presence2/telepathyclient.py b/services/presence2/telepathyclient.py deleted file mode 100644 index 34843bd6..00000000 --- a/services/presence2/telepathyclient.py +++ /dev/null @@ -1,194 +0,0 @@ - -import dbus.glib -import gobject - -from telepathy.client import ConnectionManager, ManagerRegistry, Connection, Channel -from telepathy.interfaces import ( - CONN_MGR_INTERFACE, CONN_INTERFACE, CHANNEL_TYPE_CONTACT_LIST, CHANNEL_INTERFACE_GROUP, CONN_INTERFACE_ALIASING, - CONN_INTERFACE_AVATARS, CONN_INTERFACE_PRESENCE) -from telepathy.constants import ( - CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT, - CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, CONNECTION_STATUS_CONNECTING, - CONNECTION_HANDLE_TYPE_LIST, CONNECTION_HANDLE_TYPE_CONTACT) - -loop = None - -import buddy - -class TelepathyClient(gobject.GObject): - __gsignals__ = { - 'contact-online':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), - 'contact-offline':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - } - - def __init__(self, conn): - gobject.GObject.__init__(self) - - self._online_contacts = set() - - conn[CONN_INTERFACE].connect_to_signal('StatusChanged', - self._status_changed_cb) - - # hack - conn._valid_interfaces.add(CONN_INTERFACE_PRESENCE) - conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate', - self._presence_update_cb) - - self.conn = conn - - def _request_list_channel(self, name): - handle = self.conn[CONN_INTERFACE].RequestHandles( - CONNECTION_HANDLE_TYPE_LIST, [name])[0] - chan_path = self.conn[CONN_INTERFACE].RequestChannel( - CHANNEL_TYPE_CONTACT_LIST, CONNECTION_HANDLE_TYPE_LIST, - handle, True) - channel = Channel(self.conn._dbus_object._named_service, chan_path) - # hack - channel._valid_interfaces.add(CHANNEL_INTERFACE_GROUP) - return channel - - def _connected_cb(self): - # the group of contacts who may receive your presence - publish = self._request_list_channel('publish') - publish_handles, local_pending, remote_pending = publish[CHANNEL_INTERFACE_GROUP].GetAllMembers() - - # the group of contacts for whom you wish to receive presence - subscribe = self._request_list_channel('subscribe') - subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers() - - if local_pending: - # accept pending subscriptions - #print 'pending: %r' % local_pending - publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '') - - not_subscribed = list(set(publish_handles) - set(subscribe_handles)) - self_handle = self.conn[CONN_INTERFACE].GetSelfHandle() - self._online_contacts.add(self_handle) - - for handle in not_subscribed: - # request subscriptions from people subscribed to us if we're not subscribed to them - subscribe[CHANNEL_INTERFACE_GROUP].AddMembers([self_handle], '') - - # hack - self.conn._valid_interfaces.add(CONN_INTERFACE_ALIASING) - - if CONN_INTERFACE_ALIASING in self.conn: - aliases = self.conn[CONN_INTERFACE_ALIASING].RequestAliases(subscribe_handles) - else: - aliases = self.conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, subscribe_handles) - - #for handle, alias in zip(subscribe_handles, aliases): - # print alias - # self.buddies[handle].alias = alias - - # hack - self.conn._valid_interfaces.add(CONN_INTERFACE_AVATARS) - - #if CONN_INTERFACE_AVATARS in self.conn: - # #tokens = self.conn[CONN_INTERFACE_AVATARS].RequestAvatarTokens(subscribe_handles) - - # #for handle, token in zip(subscribe_handles, tokens): - # for handle in subscribe_handles: - # avatar, mime_type = self.conn[CONN_INTERFACE_AVATARS].RequestAvatar(handle) - # self.buddies[handle].avatar = ''.join(map(chr, avatar)) - - # import gtk - # window = gtk.Window() - # window.set_title(self.buddies[handle].alias) - # loader = gtk.gdk.PixbufLoader() - # loader.write(self.buddies[handle].avatar) - # loader.close() - # image = gtk.Image() - # image.set_from_pixbuf(loader.get_pixbuf()) - # window.add(image) - # window.show_all() - - def _status_changed_cb(self, state, reason): - if state == CONNECTION_STATUS_CONNECTING: - print 'connecting' - elif state == CONNECTION_STATUS_CONNECTED: - print 'connected' - self._connected_cb() - elif state == CONNECTION_STATUS_DISCONNECTED: - print 'disconnected' - loop.quit() - - def run(self): - # If the connection is already connected query initial contacts - conn_status = self.conn[CONN_INTERFACE].GetStatus() - if conn_status == CONNECTION_STATUS_CONNECTED: - self._connected_cb() - subscribe = self._request_list_channel('subscribe') - subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers() - self.conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles) - elif conn_status == CONNECTION_STATUS_CONNECTING: - pass - else: - self.conn[CONN_INTERFACE].Connect() - - def disconnect(self): - 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" - - 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" - - # TODO: use the OLPC interface to get the key - key = handle - - self._online_contacts.add(handle) - self.emit("contact-online", handle, key) - - def _presence_update_cb(self, presence): - for handle in presence: - timestamp, statuses = presence[handle] - - name = self.conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] - online = handle in self._online_contacts - - for status, params in statuses.items(): - if not online and status in ["available", "away", "brb", "busy", "dnd", "xa"]: - self._contact_go_online(handle) - elif online and status in ["offline", "invisible"]: - self._contact_go_offline(handle) - -if __name__ == '__main__': - import logging - logging.basicConfig() - - registry = ManagerRegistry() - registry.LoadManagers() - mgr = registry.GetManager('gabble') - protocol = 'jabber' - account = { - 'account': 'olpc@collabora.co.uk', - 'password': 'learn', - 'server': 'light.bluelinux.co.uk' - } - loop = gobject.MainLoop() - conn_bus_name, conn_object_path = \ - mgr[CONN_MGR_INTERFACE].RequestConnection(protocol, account) - print conn_bus_name - print conn_object_path - conn = Connection(conn_bus_name, conn_object_path) - client = TelepathyClient(conn) - - try: - loop.run() - finally: - try: - #conn[CONN_INTERFACE].Disconnect() - client.disconnect() - except: - pass - diff --git a/sugar/profile.py b/sugar/profile.py index 43f3657c..f34e390b 100644 --- a/sugar/profile.py +++ b/sugar/profile.py @@ -19,6 +19,7 @@ import logging from ConfigParser import ConfigParser from sugar import env +from sugar import util from sugar.graphics.xocolor import XoColor class _Profile(object): @@ -26,6 +27,7 @@ class _Profile(object): self.name = None self.color = None self.pubkey = None + self.privkey_hash = None self._load() def update(self): @@ -45,6 +47,7 @@ class _Profile(object): del cp self._load_pubkey() + self._hash_private_key() def _load_pubkey(self): self.pubkey = None @@ -68,6 +71,32 @@ class _Profile(object): if not self.pubkey: logging.error("Error parsing public key.") + def _hash_private_key(self): + self.privkey_hash = None + + key_path = os.path.join(env.get_profile_path(), 'owner.key') + try: + f = open(key_path, "r") + lines = f.readlines() + f.close() + except IOError, e: + logging.error("Error reading private key: %s" % e) + return + + key = "" + for l in lines: + l = l.strip() + if l.startswith("-----BEGIN DSA PRIVATE KEY-----"): + continue + if l.startswith("-----END DSA PRIVATE KEY-----"): + continue + key += l + if not len(key): + logging.error("Error parsing public key.") + + # hash it + key_hash = util._sha_data(key) + self.privkey_hash = util.printable_hash(key_hash) def get_nick_name(): return _profile.name @@ -78,6 +107,9 @@ def get_color(): def get_pubkey(): return _profile.pubkey +def get_private_key_hash(): + return _profile.privkey_hash + def update(): _profile.update()