diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2007-06-06 15:29:46 (GMT) |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2007-06-06 15:29:46 (GMT) |
commit | 129ada9101d733130f8b0e1d56bae140b8f9327c (patch) | |
tree | 8a38bd20dc31c5311919e4241b864a46e0b33558 | |
parent | dee75374626528ea3398d2ac67bf852c8a98838b (diff) |
services/presence/: remove. Use projects/presence-service git repo instead
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | services/Makefile.am | 2 | ||||
-rw-r--r-- | services/presence/Makefile.am | 31 | ||||
-rw-r--r-- | services/presence/__init__.py | 36 | ||||
-rw-r--r-- | services/presence/activity.py | 715 | ||||
-rw-r--r-- | services/presence/buddy.py | 638 | ||||
-rw-r--r-- | services/presence/buddyiconcache.py | 105 | ||||
-rw-r--r-- | services/presence/linklocal_plugin.py | 27 | ||||
-rw-r--r-- | services/presence/org.laptop.Sugar.Presence.service.in | 4 | ||||
-rw-r--r-- | services/presence/presenceservice.py | 517 | ||||
-rw-r--r-- | services/presence/pstest.py | 317 | ||||
-rw-r--r-- | services/presence/psutils.py | 259 | ||||
-rw-r--r-- | services/presence/server_plugin.py | 1171 | ||||
-rwxr-xr-x | services/presence/sugar-presence-service | 63 | ||||
-rw-r--r-- | services/presence/test_psutils.py | 12 |
15 files changed, 1 insertions, 3897 deletions
diff --git a/configure.ac b/configure.ac index e346316..3c9bb7b 100644 --- a/configure.ac +++ b/configure.ac @@ -43,7 +43,6 @@ data/Makefile lib/Makefile lib/xdgmime/Makefile services/Makefile -services/presence/Makefile services/clipboard/Makefile shell/Makefile shell/extensions/Makefile diff --git a/services/Makefile.am b/services/Makefile.am index 867a7cd..7d8e351 100644 --- a/services/Makefile.am +++ b/services/Makefile.am @@ -1 +1 @@ -SUBDIRS = presence clipboard console +SUBDIRS = clipboard console diff --git a/services/presence/Makefile.am b/services/presence/Makefile.am deleted file mode 100644 index 44b2be7..0000000 --- a/services/presence/Makefile.am +++ /dev/null @@ -1,31 +0,0 @@ -servicedir = $(datadir)/dbus-1/services -service_in_files = org.laptop.Sugar.Presence.service.in -service_DATA = $(service_in_files:.service.in=.service) - -$(service_DATA): $(service_in_files) Makefile - @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ - -sugardir = $(pkgdatadir)/services/presence -sugar_PYTHON = \ - __init__.py \ - activity.py \ - buddy.py \ - buddyiconcache.py \ - linklocal_plugin.py \ - presenceservice.py \ - pstest.py \ - psutils.py \ - server_plugin.py - -dist_bin_SCRIPTS = sugar-presence-service - -DISTCLEANFILES = $(service_DATA) - -EXTRA_DIST = $(service_in_files) - -dist_check_SCRIPTS = test_psutils.py - -TESTS_ENVIRONMENT = \ - PYTHONPATH=$(top_srcdir):$(top_srcdir)/services/presence \ - $(PYTHON) -TESTS = $(dist_check_SCRIPTS) diff --git a/services/presence/__init__.py b/services/presence/__init__.py deleted file mode 100644 index bd64375..0000000 --- a/services/presence/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Service to track buddies and activities on the network - -Model objects: - - activity.Activity -- tracks a (shared/shareable) activity - with many properties and observable events - - buddy.Buddy -- tracks a reference to a particular actor - on the network - - buddy.GenericOwner -- actor who owns a particular - activity on the network - - buddy.ShellOwner -- actor who owns the local machine - connects to the owner module (on the server) - -Controller objects: - - presenceservice.PresenceService -- controller which connects - a networking plugin to a DBUS service. Generates events - for networking events, forwards updates/requests to the - server plugin. - - server_plugin.ServerPlugin -- implementation of networking - plugin using telepathy Python (Jabber) to provide the - underlying communications layer. Generates GObject - events that the PresenceService observes to forward onto - the DBUS clients. - -Utility machinery: - - buddyiconcache.BuddyIconCache -- caches buddy icons on disk - based on the "jid" XXX Jabber ID? of the buddy. - - psutils -- trivial function to decode int-list to characters -""" diff --git a/services/presence/activity.py b/services/presence/activity.py deleted file mode 100644 index 2eb21f6..0000000 --- a/services/presence/activity.py +++ /dev/null @@ -1,715 +0,0 @@ -# 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 -import dbus -import dbus.service -from dbus.gobject_service import ExportedGObject -from sugar import util -import logging - -from telepathy.constants import CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES -from telepathy.interfaces import (CHANNEL_INTERFACE, CHANNEL_INTERFACE_GROUP) - -_ACTIVITY_PATH = "/org/laptop/Sugar/Presence/Activities/" -_ACTIVITY_INTERFACE = "org.laptop.Sugar.Presence.Activity" - -_PROP_ID = "id" -_PROP_NAME = "name" -_PROP_COLOR = "color" -_PROP_TYPE = "type" -_PROP_VALID = "valid" -_PROP_LOCAL = "local" -_PROP_JOINED = "joined" -_PROP_CUSTOM_PROPS = "custom-props" - -_logger = logging.getLogger('s-p-s.activity') - -class Activity(ExportedGObject): - """Represents a shared activity seen on the network, or a local activity - that has been, or will be, shared onto the network. - - The activity might be public, restricted to a group, or invite-only. - """ - - __gtype_name__ = "Activity" - - __gsignals__ = { - 'validity-changed': - # The activity's validity has changed. - # An activity is valid if its name, color, type and ID have been - # set. - # Arguments: - # validity: bool - (gobject.SIGNAL_RUN_FIRST, None, [bool]), - 'disappeared': - # Nobody is in this activity any more. - # No arguments. - (gobject.SIGNAL_RUN_FIRST, None, []), - } - - __gproperties__ = { - _PROP_ID : (str, None, None, None, - gobject.PARAM_READWRITE | - gobject.PARAM_CONSTRUCT_ONLY), - _PROP_NAME : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_COLOR : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_TYPE : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_VALID : (bool, None, None, False, gobject.PARAM_READABLE), - _PROP_LOCAL : (bool, None, None, False, - gobject.PARAM_READWRITE | - gobject.PARAM_CONSTRUCT_ONLY), - _PROP_JOINED : (bool, None, None, False, gobject.PARAM_READABLE), - _PROP_CUSTOM_PROPS : (object, None, None, - gobject.PARAM_READWRITE | - gobject.PARAM_CONSTRUCT_ONLY) - } - - _RESERVED_PROPNAMES = __gproperties__.keys() - - def __init__(self, bus, object_id, ps, tp, **kwargs): - """Initializes the activity and sets its properties to default values. - - :Parameters: - `bus` : dbus.bus.BusConnection - A connection to the D-Bus session bus - `object_id` : int - PS ID for this activity, used to construct the object-path - `ps` : presenceservice.PresenceService - The presence service - `tp` : server plugin - The server plugin object (stands for "telepathy plugin") - :Keywords: - `id` : str - The globally unique activity ID (required) - `name` : str - Human-readable title for the activity - `color` : str - Activity color in #RRGGBB,#RRGGBB (stroke,fill) format - `type` : str - D-Bus service name representing the activity type - `local : bool - If True, this activity was initiated locally and is not - (yet) advertised on the network - (FIXME: is this description right?) - `custom-props` : dict - Activity-specific properties - """ - - if not object_id or not isinstance(object_id, int): - raise ValueError("object id must be a valid number") - if not tp: - raise ValueError("telepathy CM must be valid") - - self._ps = ps - self._object_id = object_id - self._object_path = dbus.ObjectPath(_ACTIVITY_PATH + - str(self._object_id)) - - self._buddies = set() - self._member_handles = set() - self._joined = False - - # the telepathy client - self._tp = tp - self._self_handle = None - self._text_channel = None - self._text_channel_group_flags = 0 - - self._valid = False - self._id = None - self._actname = None - self._color = None - self._local = False - self._type = None - self._custom_props = {} - - # ensure no reserved property names are in custom properties - cprops = kwargs.get(_PROP_CUSTOM_PROPS) - if cprops is not None: - (rprops, cprops) = self._split_properties(cprops) - if len(rprops.keys()) > 0: - raise ValueError("Cannot use reserved property names '%s'" - % ", ".join(rprops.keys())) - - if not kwargs.get(_PROP_ID): - raise ValueError("activity id is required") - if not util.validate_activity_id(kwargs[_PROP_ID]): - raise ValueError("Invalid activity id '%s'" % kwargs[_PROP_ID]) - - ExportedGObject.__init__(self, bus, self._object_path, - gobject_properties=kwargs) - if self.props.local and not self.props.valid: - raise RuntimeError("local activities require color, type, and " - "name") - - # If not yet valid, query activity properties - if not self.props.valid: - tp.update_activity_properties(self._id) - - def do_get_property(self, pspec): - """Gets the value of a property associated with this activity. - - pspec -- Property specifier - - returns The value of the given property. - """ - - if pspec.name == _PROP_ID: - return self._id - elif pspec.name == _PROP_NAME: - return self._actname - elif pspec.name == _PROP_COLOR: - return self._color - elif pspec.name == _PROP_TYPE: - return self._type - elif pspec.name == _PROP_VALID: - return self._valid - elif pspec.name == _PROP_JOINED: - return self._joined - elif pspec.name == _PROP_LOCAL: - return self._local - - def do_set_property(self, pspec, value): - """Sets the value of a property associated with this activity. - - pspec -- Property specifier - value -- Desired value - - Note that the "type" property can be set only once; attempting to set - it to something different later will raise a RuntimeError. - - """ - if pspec.name == _PROP_ID: - if self._id: - raise RuntimeError("activity ID is already set") - self._id = value - elif pspec.name == _PROP_NAME: - self._actname = value - elif pspec.name == _PROP_COLOR: - self._color = value - elif pspec.name == _PROP_TYPE: - if self._type: - raise RuntimeError("activity type is already set") - self._type = value - elif pspec.name == _PROP_JOINED: - self._joined = value - elif pspec.name == _PROP_LOCAL: - self._local = value - elif pspec.name == _PROP_CUSTOM_PROPS: - if not value: - value = {} - (rprops, cprops) = self._split_properties(value) - self._custom_props = {} - for (key, dvalue) in cprops.items(): - self._custom_props[str(key)] = str(dvalue) - - self._update_validity() - - def _update_validity(self): - """Sends a "validity-changed" signal if this activity's validity has - changed. - - Determines whether this activity's status has changed from valid to - invalid, or invalid to valid, and emits a "validity-changed" signal - if either is true. "Valid" means that the object's type, ID, name, - colour and type properties have all been set to something valid - (i.e., not "None"). - - """ - try: - old_valid = self._valid - if self._color and self._actname and self._id and self._type: - self._valid = True - else: - self._valid = False - - if old_valid != self._valid: - self.emit("validity-changed", self._valid) - except AttributeError: - self._valid = False - - # dbus signals - @dbus.service.signal(_ACTIVITY_INTERFACE, - signature="o") - def BuddyJoined(self, buddy_path): - """Generates DBUS signal when a buddy joins this activity. - - buddy_path -- DBUS path to buddy object - """ - pass - - @dbus.service.signal(_ACTIVITY_INTERFACE, - signature="o") - def BuddyLeft(self, buddy_path): - """Generates DBUS signal when a buddy leaves this activity. - - buddy_path -- DBUS path to buddy object - """ - pass - - @dbus.service.signal(_ACTIVITY_INTERFACE, - signature="o") - def NewChannel(self, channel_path): - """Generates DBUS signal when a new channel is created for this - activity. - - channel_path -- DBUS path to new channel - - XXX - what is this supposed to do? Who is supposed to call it? - What is the channel path? Right now this is never called. - - """ - pass - - # dbus methods - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="s") - def GetId(self): - """DBUS method to get this activity's (randomly generated) unique ID - - :Returns: Activity ID as a string - """ - return self.props.id - - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="s") - def GetColor(self): - """DBUS method to get this activity's colour - - :Returns: Activity colour as a string in the format #RRGGBB,#RRGGBB - """ - return self.props.color - - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="s") - def GetType(self): - """DBUS method to get this activity's type - - :Returns: Activity type as a string, in the same form as a D-Bus - well-known name - """ - return self.props.type - - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="", - async_callbacks=('async_cb', 'async_err_cb')) - def Join(self, async_cb, async_err_cb): - """DBUS method to for the local user to attempt to join the activity - - async_cb -- Callback method to be called if join attempt is successful - async_err_cb -- Callback method to be called if join attempt is - unsuccessful - - """ - self.join(async_cb, async_err_cb) - - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="ao") - def GetJoinedBuddies(self): - """DBUS method to return a list of valid buddies who are joined in - this activity - - :Returns: - A list of buddy object paths corresponding to those buddies - in this activity who are 'valid' (i.e. for whom we have complete - information) - """ - ret = [] - for buddy in self._buddies: - if buddy.props.valid: - ret.append(buddy.object_path()) - return ret - - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="soao") - def GetChannels(self): - """DBUS method to get the list of channels associated with this - activity - - :Returns: - a tuple containing: - - the D-Bus well-known service name of the connection - (FIXME: this is redundant; in Telepathy it can be derived - from that of the connection) - - the D-Bus object path of the connection - - a list of D-Bus object paths representing the channels - associated with this activity - """ - return self.get_channels() - - @dbus.service.method(_ACTIVITY_INTERFACE, - in_signature="", out_signature="s") - def GetName(self): - """DBUS method to get this activity's name - - returns Activity name - """ - return self.props.name - - # methods - def object_path(self): - """Retrieves our dbus.ObjectPath object - - returns DBUS ObjectPath object - """ - return self._object_path - - def get_joined_buddies(self): - """Local method to return a list of valid buddies who are joined in - this activity - - This method is called by the PresenceService on the local machine. - - returns A list of buddy objects - """ - ret = [] - for buddy in self._buddies: - if buddy.props.valid: - ret.append(buddy) - return ret - - def buddy_apparently_joined(self, buddy): - """Adds a buddy to this activity and sends a BuddyJoined signal, - unless we can already see who's in the activity by being in it - ourselves. - - buddy -- Buddy object representing the buddy being added - - Adds a buddy to this activity if the buddy is not already in the - buddy list. - - If this activity is "valid", a BuddyJoined signal is also sent. - This method is called by the PresenceService on the local machine. - - """ - if not self._joined: - self._add_buddies((buddy,)) - - def _add_buddies(self, buddies): - buddies = set(buddies) - - # disregard any who are already there - buddies -= self._buddies - - self._buddies |= buddies - - for buddy in buddies: - buddy.add_activity(self) - if self.props.valid: - self.BuddyJoined(buddy.object_path()) - - def _remove_buddies(self, buddies): - buddies = set(buddies) - - # disregard any who are not already there - buddies &= self._buddies - - self._buddies -= buddies - - for buddy in buddies: - buddy.remove_activity(self) - if self.props.valid: - self.BuddyJoined(buddy.object_path()) - - if not self._buddies: - self.emit('disappeared') - - def buddy_apparently_left(self, buddy): - """Removes a buddy from this activity and sends a BuddyLeft signal, - unless we can already see who's in the activity by being in it - ourselves. - - buddy -- Buddy object representing the buddy being removed - - Removes a buddy from this activity if the buddy is in the buddy list. - If this activity is "valid", a BuddyLeft signal is also sent. - This method is called by the PresenceService on the local machine. - """ - if not self._joined: - self._remove_buddies((buddy,)) - - def _text_channel_group_flags_changed_cb(self, flags): - self._text_channel_group_flags = flags - - def _handle_share_join(self, tp, text_channel): - """Called when a join to a network activity was successful. - - Called by the _shared_cb and _joined_cb methods. - """ - if not text_channel: - _logger.debug("Error sharing: text channel was None, shouldn't " - "happen") - raise RuntimeError("Plugin returned invalid text channel") - - self._text_channel = text_channel - self._text_channel[CHANNEL_INTERFACE].connect_to_signal('Closed', - self._text_channel_closed_cb) - if CHANNEL_INTERFACE_GROUP in self._text_channel: - group = self._text_channel[CHANNEL_INTERFACE_GROUP] - - # FIXME: make these method calls async? - - group.connect_to_signal('GroupFlagsChanged', - self._text_channel_group_flags_changed_cb) - self._text_channel_group_flags = group.GetGroupFlags() - - self._self_handle = group.GetSelfHandle() - - # by the time we hook this, we need to know the group flags - group.connect_to_signal('MembersChanged', - self._text_channel_members_changed_cb) - # bootstrap by getting the current state. This is where we find - # out whether anyone was lying to us in their PEP info - members = set(group.GetMembers()) - added = members - self._member_handles - removed = self._member_handles - members - if added or removed: - self._text_channel_members_changed_cb('', added, removed, - (), (), 0, 0) - - # if we can see any member handles, we're probably able to see - # all members, so can stop caring about PEP announcements for this - # activity - self._joined = (self._self_handle in self._member_handles) - else: - self._joined = True - - return True - - def _shared_cb(self, tp, activity_id, text_channel, exc, userdata): - """XXX - not documented yet - """ - if activity_id != self.props.id: - # Not for us - return - - (sigid, owner, async_cb, async_err_cb) = userdata - self._tp.disconnect(sigid) - - if exc: - _logger.debug("Share of activity %s failed: %s" % (self._id, exc)) - async_err_cb(exc) - else: - self._handle_share_join(tp, text_channel) - self.send_properties() - owner.add_activity(self) - async_cb(dbus.ObjectPath(self._object_path)) - _logger.debug("Share of activity %s succeeded." % self._id) - - def _share(self, (async_cb, async_err_cb), owner): - """XXX - not documented yet - - XXX - This method is called externally by the PresenceService - despite the fact that this is supposed to be an internal method! - """ - _logger.debug("Starting share of activity %s" % self._id) - if self._joined: - async_err_cb(RuntimeError("Already shared activity %s" - % self.props.id)) - return - sigid = self._tp.connect('activity-shared', self._shared_cb) - self._tp.share_activity(self.props.id, (sigid, owner, async_cb, - async_err_cb)) - _logger.debug("done with share attempt %s" % self._id) - - def _joined_cb(self, tp, activity_id, text_channel, exc, userdata): - """XXX - not documented yet - """ - if activity_id != self.props.id: - # Not for us - return - - (sigid, async_cb, async_err_cb) = userdata - self._tp.disconnect(sigid) - - if exc: - async_err_cb(exc) - else: - self._handle_share_join(tp, text_channel) - async_cb() - - def join(self, async_cb, async_err_cb): - """Local method for the local user to attempt to join the activity. - - async_cb -- Callback method to be called if join attempt is successful - async_err_cb -- Callback method to be called if join attempt is - unsuccessful - - The two callbacks are passed to the server_plugin ("tp") object, - which in turn passes them back as parameters in a callback to the - _joined_cb method; this callback is set up within this method. - """ - if self._joined: - async_err_cb(RuntimeError("Already joined activity %s" - % self.props.id)) - return - sigid = self._tp.connect('activity-joined', self._joined_cb) - self._tp.join_activity(self.props.id, (sigid, async_cb, async_err_cb)) - - def get_channels(self): - """Local method to get the list of channels associated with this - activity - - returns XXX - expected a list of channels, instead returning a tuple? - """ - conn = self._tp.get_connection() - # FIXME add tubes and others channels - return (str(conn.service_name), conn.object_path, - [self._text_channel.object_path]) - - def leave(self): - """Local method called when the user wants to leave the activity. - - (XXX - doesn't appear to be called anywhere!) - - """ - if self._joined: - self._text_channel[CHANNEL_INTERFACE].Close() - - def _text_channel_members_changed_cb(self, message, added, removed, - local_pending, remote_pending, - actor, reason): - # Note: D-Bus calls this with list arguments, but after GetMembers() - # we call it with set and tuple arguments; we cope with any iterable. - - if (self._text_channel_group_flags & - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES): - map_chan = self._text_channel - else: - # we have global handles here - map_chan = None - - # disregard any who are already there - added = set(added) - added -= self._member_handles - self._member_handles |= added - - # for added people, we need a Buddy object - added_buddies = self._ps.map_handles_to_buddies(self._tp, - map_chan, - added) - self._add_buddies(added_buddies.itervalues()) - - # we treat all pending members as if they weren't there - removed = set(removed) - removed |= set(local_pending) - removed |= set(remote_pending) - # disregard any who aren't already there - removed &= self._member_handles - self._member_handles -= removed - - # for removed people, don't bother creating a Buddy just so we can - # say it left. If we don't already have a Buddy object for someone, - # then obviously they're not in self._buddies! - removed_buddies = self._ps.map_handles_to_buddies(self._tp, - map_chan, - removed, - create=False) - self._remove_buddies(removed_buddies.itervalues()) - - # if we were among those removed, we'll have to start believing - # the spoofable PEP-based activity tracking again. - if self._self_handle not in self._member_handles: - self._joined = False - - def _text_channel_closed_cb(self): - """Callback method called when the text channel is closed. - - This callback is set up in the _handle_share_join method. - """ - self._joined = False - self._self_handle = None - self._text_channel = None - - def send_properties(self): - """Tells the Telepathy server what the properties of this activity are. - - """ - props = {} - props['name'] = self._actname - props['color'] = self._color - props['type'] = self._type - - # Add custom properties - for (key, value) in self._custom_props.items(): - props[key] = value - - self._tp.set_activity_properties(self.props.id, props) - - def set_properties(self, properties): - """Sets name, colour and/or type properties for this activity all - at once. - - properties - Dictionary object containing properties keyed by - property names - - Note that if any of the name, colour and/or type property values is - changed from what it originally was, the update_validity method will - be called, resulting in a "validity-changed" signal being generated. - Called by the PresenceService on the local machine. - """ - changed = False - # split reserved properties from activity-custom properties - (rprops, cprops) = self._split_properties(properties) - if _PROP_NAME in rprops.keys(): - name = rprops[_PROP_NAME] - if name != self._actname: - self._actname = name - changed = True - - if _PROP_COLOR in rprops.keys(): - color = rprops[_PROP_COLOR] - if color != self._color: - self._color = color - changed = True - - if _PROP_TYPE in rprops.keys(): - type = rprops[_PROP_TYPE] - if type != self._type: - # Type can never be changed after first set - if self._type: - _logger.debug("Activity type changed by network; this " - "is illegal") - else: - self._type = type - changed = True - - # Set custom properties - if len(cprops.keys()) > 0: - self.props.custom_props = cprops - - if changed: - self._update_validity() - - def _split_properties(self, properties): - """Extracts reserved properties. - - properties - Dictionary object containing properties keyed by - property names - - returns a tuple of 2 dictionaries, reserved properties and custom - properties - """ - rprops = {} - cprops = {} - for (key, value) in properties.items(): - if key in self._RESERVED_PROPNAMES: - rprops[key] = value - else: - cprops[key] = value - return (rprops, cprops) diff --git a/services/presence/buddy.py b/services/presence/buddy.py deleted file mode 100644 index 82a9b44..0000000 --- a/services/presence/buddy.py +++ /dev/null @@ -1,638 +0,0 @@ -"""An "actor" on the network, whether remote or local""" -# 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 os -import gobject -import dbus -import dbus.service -from dbus.gobject_service import ExportedGObject -import psutils - -from sugar import env, profile -import logging - -_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/" -_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy" -_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner" - -_PROP_NICK = "nick" -_PROP_KEY = "key" -_PROP_ICON = "icon" -_PROP_CURACT = "current-activity" -_PROP_COLOR = "color" -_PROP_OWNER = "owner" -_PROP_VALID = "valid" -_PROP_OBJID = 'objid' - -# Will go away soon -_PROP_IP4_ADDRESS = "ip4-address" - -_logger = logging.getLogger('s-p-s.buddy') - - -class Buddy(ExportedGObject): - """Person on the network (tracks properties and shared activites) - - The Buddy is a collection of metadata describing a particular - actor/person on the network. The Buddy object tracks a set of - activities which the actor has shared with the presence service. - - Buddies have a "valid" property which is used to flag Buddies - which are no longer reachable. That is, a Buddy may represent - a no-longer reachable target on the network. - - The Buddy emits GObject events that the PresenceService uses - to track changes in its status. - - Attributes: - - _activities -- dictionary mapping activity ID to - activity.Activity objects - handles -- dictionary mapping Telepathy client plugin to - contact handle (an integer representing the JID or unique ID); - channel-specific handles do not appear here - """ - - __gsignals__ = { - 'validity-changed': - # The buddy's validity changed. - # Validity starts off False, and becomes True when the buddy - # has a color, a nick and a key. - # * the new validity: bool - (gobject.SIGNAL_RUN_FIRST, None, [bool]), - 'property-changed': - # One of the buddy's properties has changed. - # * those properties that have changed: - # dict { str => object } - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'icon-changed': - # The buddy's icon changed. - # * the bytes of the icon: str - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'disappeared': - # The buddy is offline (has no Telepathy handles and is not the - # Owner) - (gobject.SIGNAL_RUN_FIRST, None, []), - } - - __gproperties__ = { - _PROP_KEY : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_ICON : (object, None, None, gobject.PARAM_READWRITE), - _PROP_NICK : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_COLOR : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_CURACT : (str, None, None, None, gobject.PARAM_READWRITE), - _PROP_VALID : (bool, None, None, False, gobject.PARAM_READABLE), - _PROP_OWNER : (bool, None, None, False, gobject.PARAM_READABLE), - _PROP_OBJID : (str, None, None, None, gobject.PARAM_READABLE), - _PROP_IP4_ADDRESS : (str, None, None, None, gobject.PARAM_READWRITE) - } - - def __init__(self, bus, object_id, **kwargs): - """Initialize the Buddy object - - bus -- connection to the D-Bus session bus - object_id -- the buddy's unique identifier, either based on their - key-ID or JID - kwargs -- used to initialize the object's properties - - constructs a DBUS "object path" from the _BUDDY_PATH - and object_id - """ - - self._object_id = object_id - self._object_path = dbus.ObjectPath(_BUDDY_PATH + object_id) - - self._activities = {} # Activity ID -> Activity - self._activity_sigids = {} - self.handles = {} # tp client -> handle - - self._valid = False - self._owner = False - self._key = None - self._icon = '' - self._current_activity = None - self._nick = None - self._color = None - self._ip4_address = None - - _ALLOWED_INIT_PROPS = [_PROP_NICK, _PROP_KEY, _PROP_ICON, - _PROP_CURACT, _PROP_COLOR, _PROP_IP4_ADDRESS] - for (key, value) in kwargs.items(): - if key not in _ALLOWED_INIT_PROPS: - _logger.debug("Invalid init property '%s'; ignoring..." % key) - del kwargs[key] - - # Set icon after superclass init, because it sends DBus and GObject - # signals when set - icon_data = None - if kwargs.has_key(_PROP_ICON): - icon_data = kwargs[_PROP_ICON] - del kwargs[_PROP_ICON] - - ExportedGObject.__init__(self, bus, self._object_path, - gobject_properties=kwargs) - - if icon_data: - self.props.icon = icon_data - - def do_get_property(self, pspec): - """Retrieve current value for the given property specifier - - pspec -- property specifier with a "name" attribute - """ - if pspec.name == _PROP_OBJID: - return self._object_id - elif pspec.name == _PROP_KEY: - return self._key - elif pspec.name == _PROP_ICON: - return self._icon - elif pspec.name == _PROP_NICK: - return self._nick - elif pspec.name == _PROP_COLOR: - return self._color - elif pspec.name == _PROP_CURACT: - if not self._current_activity: - return None - if not self._activities.has_key(self._current_activity): - return None - return self._current_activity - elif pspec.name == _PROP_VALID: - return self._valid - elif pspec.name == _PROP_OWNER: - return self._owner - elif pspec.name == _PROP_IP4_ADDRESS: - return self._ip4_address - - def do_set_property(self, pspec, value): - """Set given property - - pspec -- property specifier with a "name" attribute - value -- value to set - - emits 'icon-changed' signal on icon setting - calls _update_validity on all calls - """ - if pspec.name == _PROP_ICON: - if str(value) != self._icon: - self._icon = str(value) - self.IconChanged(self._icon) - self.emit('icon-changed', self._icon) - elif pspec.name == _PROP_NICK: - self._nick = value - elif pspec.name == _PROP_COLOR: - self._color = value - elif pspec.name == _PROP_CURACT: - self._current_activity = value - elif pspec.name == _PROP_KEY: - if self._key: - raise RuntimeError("Key already set.") - self._key = value - elif pspec.name == _PROP_IP4_ADDRESS: - self._ip4_address = value - - self._update_validity() - - # dbus signals - @dbus.service.signal(_BUDDY_INTERFACE, - signature="ay") - def IconChanged(self, icon_data): - """Generates DBUS signal with icon_data""" - - @dbus.service.signal(_BUDDY_INTERFACE, - signature="o") - def JoinedActivity(self, activity_path): - """Generates DBUS signal when buddy joins activity - - activity_path -- DBUS path to the activity object - """ - - @dbus.service.signal(_BUDDY_INTERFACE, - signature="o") - def LeftActivity(self, activity_path): - """Generates DBUS signal when buddy leaves activity - - activity_path -- DBUS path to the activity object - """ - - @dbus.service.signal(_BUDDY_INTERFACE, - signature="a{sv}") - def PropertyChanged(self, updated): - """Generates DBUS signal when buddy's property changes - - updated -- updated property-set (dictionary) with the - Buddy's property (changed) values. Note: not the - full set of properties, just the changes. - """ - - def add_telepathy_handle(self, tp_client, handle): - """Add a Telepathy handle.""" - conn = tp_client.get_connection() - self.TelepathyHandleAdded(conn.service_name, conn.object_path, handle) - self.handles[tp_client] = handle - - @dbus.service.signal(_BUDDY_INTERFACE, signature='sou') - def TelepathyHandleAdded(self, tp_conn_name, tp_conn_path, handle): - """Another Telepathy handle has become associated with the buddy. - - This must only be emitted for non-channel-specific handles. - - tp_conn_name -- The bus name at which the Telepathy connection may be - found - tp_conn_path -- The object path at which the Telepathy connection may - be found - handle -- The handle of type CONTACT, which is not channel-specific, - newly associated with the buddy - """ - - def remove_telepathy_handle(self, tp_client, handle): - """Remove a Telepathy handle.""" - conn = tp_client.get_connection() - my_handle = self.handles.get(tp_client, 0) - if my_handle == handle: - del self.handles[tp_client] - self.TelepathyHandleRemoved(conn.service_name, conn.object_path, - handle) - # the Owner can't disappear - that would be silly - if not self.handles and not self._owner: - self.emit('disappeared') - else: - _logger.debug('Telepathy handle %u supposedly removed, but ' - 'my handle on that connection is %u - ignoring', - handle, my_handle) - - @dbus.service.signal(_BUDDY_INTERFACE, signature='sou') - def TelepathyHandleRemoved(self, tp_conn_name, tp_conn_path, handle): - """A Telepathy handle has ceased to be associated with the buddy, - probably because that contact went offline. - - The parameters are the same as for TelepathyHandleAdded. - """ - - # dbus methods - @dbus.service.method(_BUDDY_INTERFACE, - in_signature="", out_signature="ay") - def GetIcon(self): - """Retrieve Buddy's icon data - - returns empty string or dbus.ByteArray - """ - if not self.props.icon: - return "" - return dbus.ByteArray(self.props.icon) - - @dbus.service.method(_BUDDY_INTERFACE, - in_signature="", out_signature="ao") - def GetJoinedActivities(self): - """Retrieve set of Buddy's joined activities (paths) - - returns list of dbus service paths for the Buddy's joined - activities - """ - acts = [] - for act in self.get_joined_activities(): - if act.props.valid: - acts.append(act.object_path()) - return acts - - @dbus.service.method(_BUDDY_INTERFACE, - in_signature="", out_signature="a{sv}") - def GetProperties(self): - """Retrieve set of Buddy's properties - - returns dictionary of - nick : str(nickname) - owner : bool( whether this Buddy is an owner??? ) - XXX what is the owner flag for? - key : str(public-key) - color: Buddy's icon colour - XXX what type? - current-activity: Buddy's current activity_id, or - "" if no current activity - """ - props = {} - props[_PROP_NICK] = self.props.nick - props[_PROP_OWNER] = self.props.owner - props[_PROP_KEY] = self.props.key - props[_PROP_COLOR] = self.props.color - - if self.props.ip4_address: - props[_PROP_IP4_ADDRESS] = self.props.ip4_address - else: - props[_PROP_IP4_ADDRESS] = "" - - if self.props.current_activity: - props[_PROP_CURACT] = self.props.current_activity - else: - props[_PROP_CURACT] = "" - return props - - @dbus.service.method(_BUDDY_INTERFACE, - in_signature='', out_signature='a(sou)') - def GetTelepathyHandles(self): - """Return a list of non-channel-specific Telepathy contact handles - associated with this Buddy. - - :Returns: - An array of triples (connection well-known bus name, connection - object path, handle). - """ - ret = [] - for plugin in self.handles: - conn = plugin.get_connection() - ret.append((str(conn.service_name), conn.object_path, - self.handles[plugin])) - - # methods - def object_path(self): - """Retrieve our dbus.ObjectPath object""" - return dbus.ObjectPath(self._object_path) - - def _activity_validity_changed_cb(self, activity, valid): - """Join or leave the activity when its validity changes""" - if valid: - self.JoinedActivity(activity.object_path()) - else: - self.LeftActivity(activity.object_path()) - - def add_activity(self, activity): - """Add an activity to the Buddy's set of activities - - activity -- activity.Activity instance - - calls JoinedActivity - """ - actid = activity.props.id - if self._activities.has_key(actid): - return - self._activities[actid] = activity - # join/leave activity when it's validity changes - sigid = activity.connect("validity-changed", - self._activity_validity_changed_cb) - self._activity_sigids[actid] = sigid - if activity.props.valid: - self.JoinedActivity(activity.object_path()) - - def remove_activity(self, activity): - """Remove the activity from the Buddy's set of activities - - activity -- activity.Activity instance - - calls LeftActivity - """ - actid = activity.props.id - if not self._activities.has_key(actid): - return - activity.disconnect(self._activity_sigids[actid]) - del self._activity_sigids[actid] - del self._activities[actid] - if activity.props.valid: - self.LeftActivity(activity.object_path()) - - def get_joined_activities(self): - """Retrieves list of still-valid activity objects""" - acts = [] - for act in self._activities.values(): - acts.append(act) - return acts - - def set_properties(self, properties): - """Set the given set of properties on the object - - properties -- set of property values to set - - if no change, no events generated - if change, generates property-changed and - calls _update_validity - """ - changed = False - changed_props = {} - if _PROP_NICK in properties: - nick = properties[_PROP_NICK] - if nick != self._nick: - self._nick = nick - changed_props[_PROP_NICK] = nick - changed = True - if _PROP_COLOR in properties: - color = properties[_PROP_COLOR] - if color != self._color: - self._color = color - changed_props[_PROP_COLOR] = color - changed = True - if _PROP_CURACT in properties: - curact = properties[_PROP_CURACT] - if curact != self._current_activity: - self._current_activity = curact - changed_props[_PROP_CURACT] = curact - changed = True - if _PROP_IP4_ADDRESS in properties: - ip4addr = properties[_PROP_IP4_ADDRESS] - if ip4addr != self._ip4_address: - self._ip4_address = ip4addr - changed_props[_PROP_IP4_ADDRESS] = ip4addr - changed = True - if _PROP_KEY in properties: - # don't allow key to be set more than once - if self._key is None: - key = properties[_PROP_KEY] - if key is not None: - self._key = key - changed_props[_PROP_KEY] = key - changed = True - - if not changed or not changed_props: - return - - # Try emitting PropertyChanged before updating validity - # to avoid leaking a PropertyChanged signal before the buddy is - # actually valid the first time after creation - if self._valid: - dbus_changed = {} - for key, value in changed_props.items(): - if value: - dbus_changed[key] = value - else: - dbus_changed[key] = "" - self.PropertyChanged(dbus_changed) - - self.emit('property-changed', changed_props) - - self._update_validity() - - def _update_validity(self): - """Check whether we are now valid - - validity is True if color, nick and key are non-null - - emits validity-changed if we have changed validity - """ - try: - old_valid = self._valid - if self._color and self._nick and self._key: - self._valid = True - else: - self._valid = False - - if old_valid != self._valid: - self.emit("validity-changed", self._valid) - except AttributeError: - self._valid = False - - -class GenericOwner(Buddy): - """Common functionality for Local User-like objects - - The TestOwner wants to produce something *like* a - ShellOwner, but with randomised changes and the like. - This class provides the common features for a real - local owner and a testing one. - """ - __gtype_name__ = "GenericOwner" - - def __init__(self, ps, bus, object_id, **kwargs): - """Initialize the GenericOwner instance - - ps -- presenceservice.PresenceService object - bus -- a connection to the D-Bus session bus - object_id -- the activity's unique identifier - kwargs -- used to initialize the object's properties - - calls Buddy.__init__ - """ - self._ps = ps - self._server = kwargs.pop("server", "olpc.collabora.co.uk") - self._key_hash = kwargs.pop("key_hash", None) - self._registered = kwargs.pop("registered", False) - - self._ip4_addr_monitor = psutils.IP4AddressMonitor.get_instance() - self._ip4_addr_monitor.connect("address-changed", - self._ip4_address_changed_cb) - if self._ip4_addr_monitor.props.address: - kwargs["ip4-address"] = self._ip4_addr_monitor.props.address - - Buddy.__init__(self, bus, object_id, **kwargs) - self._owner = True - - self._bus = dbus.SessionBus() - - def _ip4_address_changed_cb(self, monitor, address): - """Handle IPv4 address change, set property to generate event""" - props = {_PROP_IP4_ADDRESS: address} - self.set_properties(props) - - def get_registered(self): - """Retrieve whether owner has registered with presence server""" - return self._registered - - def get_server(self): - """Retrieve XMPP server hostname (used by the server plugin)""" - return self._server - - def get_key_hash(self): - """Retrieve the user's private-key hash (used by the server plugin - as a password) - """ - return self._key_hash - - def set_registered(self, registered): - """Customisation point: handle the registration of the owner""" - raise RuntimeError("Subclasses must implement") - - -class ShellOwner(GenericOwner): - """Representation of the local-machine owner using Sugar's Shell - - The ShellOwner uses the Sugar Shell's dbus services to - register for updates about the user's profile description. - """ - __gtype_name__ = "ShellOwner" - - _SHELL_SERVICE = "org.laptop.Shell" - _SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner" - _SHELL_PATH = "/org/laptop/Shell" - - def __init__(self, ps, bus): - """Initialize the ShellOwner instance - - ps -- presenceservice.PresenceService object - bus -- a connection to the D-Bus session bus - - Retrieves initial property values from the profile - module. Loads the buddy icon from file as well. - XXX note: no error handling on that - - calls GenericOwner.__init__ - """ - server = profile.get_server() - key_hash = profile.get_private_key_hash() - registered = profile.get_server_registered() - key = profile.get_pubkey() - nick = profile.get_nick_name() - color = profile.get_color().to_string() - - icon_file = os.path.join(env.get_profile_path(), "buddy-icon.jpg") - f = open(icon_file, "r") - icon = f.read() - f.close() - - GenericOwner.__init__(self, ps, bus, - 'keyid/' + psutils.pubkey_to_keyid(key), - key=key, nick=nick, color=color, icon=icon, server=server, - key_hash=key_hash, registered=registered) - - # Ask to get notifications on Owner object property changes in the - # shell. If it's not currently running, no problem - we'll get the - # signals when it does run - for (signal, cb) in (('IconChanged', self._icon_changed_cb), - ('ColorChanged', self._color_changed_cb), - ('NickChanged', self._nick_changed_cb)): - self._bus.add_signal_receiver(cb, signal_name=signal, - dbus_interface=self._SHELL_OWNER_INTERFACE, - bus_name=self._SHELL_SERVICE, - path=self._SHELL_PATH) - - def set_registered(self, value): - """Handle notification that we have been registered""" - if value: - profile.set_server_registered() - - def _icon_changed_cb(self, icon): - """Handle icon change, set property to generate event""" - self.props.icon = icon - - def _color_changed_cb(self, color): - """Handle color change, set property to generate event""" - props = {_PROP_COLOR: color} - self.set_properties(props) - - def _nick_changed_cb(self, nick): - """Handle nickname change, set property to generate event""" - props = {_PROP_NICK: nick} - self.set_properties(props) - - def _cur_activity_changed_cb(self, activity_id): - """Handle current-activity change, set property to generate event - - Filters out local activities (those not in self.activites) - because the network users can't join those activities, so - the activity_id shared will be None in those cases... - """ - if not self._activities.has_key(activity_id): - # This activity is local-only - activity_id = None - props = {_PROP_CURACT: activity_id} - self.set_properties(props) diff --git a/services/presence/buddyiconcache.py b/services/presence/buddyiconcache.py deleted file mode 100644 index 9d355bb..0000000 --- a/services/presence/buddyiconcache.py +++ /dev/null @@ -1,105 +0,0 @@ -# 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 - -from sugar import env - -import os.path -import cPickle - -class BuddyIconCache(object): - """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", "cache") - - # Ensure cache directory exists - if not os.path.exists(os.path.dirname(self._cachepath)): - os.makedirs(os.path.dirname(self._cachepath)) - - if not os.path.exists(self._cachepath): - self._cache = {} - # md5 and server token of the last avatar uploaded - self._md5 = '' - self._token = '' - else: - self._load_cache() - - def _load_cache(self): - try: - self._cache, self._md5, self._token = cPickle.load(open(self._cachepath, "r")) - except: - self._cache, self._md5, self._token = {}, '', '' - - def _save_cache(self): - out = open(self._cachepath, "w") - cPickle.dump((self._cache, self._md5, self._token), 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 store_icon(self, jid, token, data): - self._cache[jid] = (token, data) - self._save_cache() - - def check_avatar(self, md5, token): - return self._md5 == md5 and self._token == token - - def set_avatar(self, md5, token): - self._md5 = md5 - self._token = token - self._save_cache() - -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 - - 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 - - - my_avatar_md5 = "111" - my_avatar_token = "222" - - if not my_cache.check_avatar(my_avatar_md5, my_avatar_token): - # upload of the new avatar - print "upload of the new avatar" - my_cache.set_avatar(my_avatar_md5, my_avatar_token) - else: - print "No need to upload the new avatar" - - if my_cache.check_avatar(my_avatar_md5, my_avatar_token): - print "No need to upload the new avatar" diff --git a/services/presence/linklocal_plugin.py b/services/presence/linklocal_plugin.py deleted file mode 100644 index b8f6445..0000000 --- a/services/presence/linklocal_plugin.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 - -class LinkLocalPlugin(gobject.GObject): - def __init__(self, registry, owner): - gobject.GObject.__init__(self) - self._registry = registry - self._owner = owner - - def cleanup(self): - pass diff --git a/services/presence/org.laptop.Sugar.Presence.service.in b/services/presence/org.laptop.Sugar.Presence.service.in deleted file mode 100644 index 70ecda6..0000000 --- a/services/presence/org.laptop.Sugar.Presence.service.in +++ /dev/null @@ -1,4 +0,0 @@ -[D-BUS Service] -Name = org.laptop.Sugar.Presence -Exec = @bindir@/sugar-presence-service - diff --git a/services/presence/presenceservice.py b/services/presence/presenceservice.py deleted file mode 100644 index 5bcfd45..0000000 --- a/services/presence/presenceservice.py +++ /dev/null @@ -1,517 +0,0 @@ -# Copyright (C) 2007, Red Hat, Inc. -# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/> -# -# 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 logging -from weakref import WeakValueDictionary - -import dbus -import dbus.service -import gobject -from dbus.gobject_service import ExportedGObject -from dbus.mainloop.glib import DBusGMainLoop -from telepathy.client import ManagerRegistry, Connection -from telepathy.interfaces import (CONN_MGR_INTERFACE, CONN_INTERFACE) -from telepathy.constants import (CONNECTION_STATUS_CONNECTING, - CONNECTION_STATUS_CONNECTED, - CONNECTION_STATUS_DISCONNECTED) - -from sugar import util - -from server_plugin import ServerPlugin -from linklocal_plugin import LinkLocalPlugin -from buddy import Buddy, ShellOwner -from activity import Activity -from psutils import pubkey_to_keyid - -_PRESENCE_SERVICE = "org.laptop.Sugar.Presence" -_PRESENCE_INTERFACE = "org.laptop.Sugar.Presence" -_PRESENCE_PATH = "/org/laptop/Sugar/Presence" - - -_logger = logging.getLogger('s-p-s.presenceservice') - - -class NotFoundError(dbus.DBusException): - def __init__(self, msg): - dbus.DBusException.__init__(self, msg) - self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound' - -class PresenceService(ExportedGObject): - __gtype_name__ = "PresenceService" - - __gsignals__ = { - 'connection-status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_BOOLEAN])) - } - - def _create_owner(self): - # Overridden by TestPresenceService - return ShellOwner(self, self._session_bus) - - def __init__(self): - self._next_object_id = 0 - self._connected = False - - # all Buddy objects - # identifier -> Buddy, GC'd when no more refs exist - self._buddies = WeakValueDictionary() - - # the online buddies for whom we know the full public key - # base64 public key -> Buddy - self._buddies_by_pubkey = {} - - # The online buddies (those who're available via some CM) - # TP plugin -> (handle -> Buddy) - self._handles_buddies = {} - - # activity id -> Activity - self._activities = {} - - self._session_bus = dbus.SessionBus() - self._session_bus.add_signal_receiver(self._connection_disconnected_cb, - signal_name="Disconnected", - dbus_interface="org.freedesktop.DBus") - - # Create the Owner object - self._owner = self._create_owner() - key = self._owner.props.key - keyid = pubkey_to_keyid(key) - self._buddies['keyid/' + keyid] = self._owner - self._buddies_by_pubkey[key] = self._owner - - self._registry = ManagerRegistry() - self._registry.LoadManagers() - - # Set up the server connection - self._server_plugin = ServerPlugin(self._registry, self._owner) - self._handles_buddies[self._server_plugin] = {} - - 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.connect('buddy-properties-changed', - self._buddy_properties_changed) - self._server_plugin.connect('buddy-activities-changed', - self._buddy_activities_changed) - self._server_plugin.connect('activity-invitation', - self._activity_invitation) - self._server_plugin.connect('private-invitation', - self._private_invitation) - self._server_plugin.connect('activity-properties-changed', - self._activity_properties_changed) - self._server_plugin.start() - - # Set up the link local connection - self._ll_plugin = LinkLocalPlugin(self._registry, self._owner) - self._handles_buddies[self._ll_plugin] = {} - - ExportedGObject.__init__(self, self._session_bus, _PRESENCE_PATH) - - # for activation to work in a race-free way, we should really - # export the bus name only after we export our initial object; - # so this comes after the parent __init__ - self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, - bus=self._session_bus) - - def _connection_disconnected_cb(self, foo=None): - """Log event when D-Bus kicks us off the bus for some reason""" - _logger.debug("Disconnected from session bus!!!") - - def _server_status_cb(self, plugin, status, reason): - - # FIXME: figure out connection status when we have a salut plugin too - old_status = self._connected - if status == CONNECTION_STATUS_CONNECTED: - self._connected = True - self._handles_buddies[plugin][plugin.self_handle] = self._owner - self._owner.add_telepathy_handle(plugin, plugin.self_handle) - else: - self._connected = False - if plugin.self_handle is not None: - self._handles_buddies.setdefault(plugin, {}).pop( - plugin.self_handle, None) - self._owner.remove_telepathy_handle(plugin, plugin.self_handle) - - if self._connected != old_status: - self.emit('connection-status', self._connected) - - def get_buddy(self, objid): - buddy = self._buddies.get(objid) - if buddy is None: - _logger.debug('Creating new buddy at .../%s', objid) - # we don't know yet this buddy - buddy = Buddy(self._session_bus, objid) - buddy.connect("validity-changed", self._buddy_validity_changed_cb) - buddy.connect("disappeared", self._buddy_disappeared_cb) - self._buddies[objid] = buddy - return buddy - - def _contact_online(self, tp, objid, handle, props): - _logger.debug('Handle %u, .../%s is now online', handle, objid) - buddy = self.get_buddy(objid) - - self._handles_buddies[tp][handle] = buddy - # store the handle of the buddy for this CM - buddy.add_telepathy_handle(tp, handle) - buddy.set_properties(props) - - def _buddy_validity_changed_cb(self, buddy, valid): - if valid: - self.BuddyAppeared(buddy.object_path()) - self._buddies_by_pubkey[buddy.props.key] = buddy - _logger.debug("New Buddy: %s (%s)", buddy.props.nick, - buddy.props.color) - else: - self.BuddyDisappeared(buddy.object_path()) - self._buddies_by_pubkey.pop(buddy.props.key, None) - _logger.debug("Buddy left: %s (%s)", buddy.props.nick, - buddy.props.color) - - def _buddy_disappeared_cb(self, buddy): - if buddy.props.valid: - self._buddy_validity_changed_cb(buddy, False) - - def _contact_offline(self, tp, handle): - if not self._handles_buddies[tp].has_key(handle): - return - - buddy = self._handles_buddies[tp].pop(handle) - # the handle of the buddy for this CM is not valid anymore - # (this might trigger _buddy_disappeared_cb if they are not visible - # via any CM) - buddy.remove_telepathy_handle(tp, handle) - - def _get_next_object_id(self): - """Increment and return the object ID counter.""" - self._next_object_id = self._next_object_id + 1 - return self._next_object_id - - def _avatar_updated(self, tp, handle, avatar): - buddy = self._handles_buddies[tp].get(handle) - if buddy and not buddy.props.owner: - _logger.debug("Buddy %s icon updated" % buddy.props.nick) - buddy.props.icon = avatar - - def _buddy_properties_changed(self, tp, handle, properties): - buddy = self._handles_buddies[tp].get(handle) - if buddy: - buddy.set_properties(properties) - _logger.debug("Buddy %s properties updated: %s", buddy.props.nick, - properties.keys()) - - def _new_activity(self, activity_id, tp): - try: - objid = self._get_next_object_id() - activity = Activity(self._session_bus, objid, self, tp, - id=activity_id) - except Exception: - # FIXME: catching bare Exception considered harmful - _logger.debug("Invalid activity:", exc_info=1) - return None - - activity.connect("validity-changed", - self._activity_validity_changed_cb) - activity.connect("disappeared", self._activity_disappeared_cb) - self._activities[activity_id] = activity - return activity - - def _activity_disappeared_cb(self, activity): - _logger.debug("activity %s disappeared" % activity.props.id) - - self.ActivityDisappeared(activity.object_path()) - del self._activities[activity.props.id] - - def _buddy_activities_changed(self, tp, contact_handle, activities): - acts = [] - for act in activities: - acts.append(str(act)) - _logger.debug("Handle %s activities changed: %s", contact_handle, acts) - buddies = self._handles_buddies[tp] - buddy = buddies.get(contact_handle) - - if not buddy: - # We don't know this buddy - # FIXME: What should we do here? - # FIXME: Do we need to check if the buddy is valid or something? - _logger.debug("contact_activities_changed: buddy unknown") - return - - old_activities = set() - for activity in buddy.get_joined_activities(): - old_activities.add(activity.props.id) - - new_activities = set(activities) - - activities_joined = new_activities - old_activities - for act in activities_joined: - _logger.debug("Handle %s joined activity %s", contact_handle, act) - activity = self._activities.get(act) - if activity is None: - # new activity, can fail - activity = self._new_activity(act, tp) - - if activity is not None: - activity.buddy_apparently_joined(buddy) - - activities_left = old_activities - new_activities - for act in activities_left: - _logger.debug("Handle %s left activity %s", contact_handle, act) - activity = self._activities.get(act) - if not activity: - continue - - activity.buddy_apparently_left(buddy) - - def _activity_invitation(self, tp, act_id): - activity = self._activities.get(act_id) - if activity: - self.ActivityInvitation(activity.object_path()) - - def _private_invitation(self, tp, chan_path): - conn = tp.get_connection() - self.PrivateInvitation(str(conn.service_name), conn.object_path, - chan_path) - - @dbus.service.signal(_PRESENCE_INTERFACE, signature="o") - def ActivityAppeared(self, activity): - pass - - @dbus.service.signal(_PRESENCE_INTERFACE, signature="o") - def ActivityDisappeared(self, activity): - pass - - @dbus.service.signal(_PRESENCE_INTERFACE, signature="o") - def BuddyAppeared(self, buddy): - pass - - @dbus.service.signal(_PRESENCE_INTERFACE, signature="o") - def BuddyDisappeared(self, buddy): - pass - - @dbus.service.signal(_PRESENCE_INTERFACE, signature="o") - def ActivityInvitation(self, activity): - pass - - @dbus.service.signal(_PRESENCE_INTERFACE, signature="soo") - def PrivateInvitation(self, bus_name, connection, channel): - pass - - @dbus.service.method(_PRESENCE_INTERFACE, in_signature='', - out_signature="ao") - def GetActivities(self): - ret = [] - for act in self._activities.values(): - if act.props.valid: - ret.append(act.object_path()) - return ret - - @dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", - out_signature="o") - def GetActivityById(self, actid): - act = self._activities.get(actid, None) - if not act or not act.props.valid: - raise NotFoundError("The activity was not found.") - return act.object_path() - - @dbus.service.method(_PRESENCE_INTERFACE, in_signature='', - out_signature="ao") - def GetBuddies(self): - # in the presence of an out_signature, dbus-python will convert - # this set into an Array automatically (because it's iterable), - # so it's easy to use for uniquification (we want to avoid returning - # buddies who're visible on both Salut and Gabble twice) - - # always include myself even if I have no handles - ret = set((self._owner,)) - - for handles_buddies in self._handles_buddies.itervalues(): - for buddy in handles_buddies.itervalues(): - if buddy.props.valid: - ret.add(buddy.object_path()) - return ret - - @dbus.service.method(_PRESENCE_INTERFACE, - in_signature="ay", out_signature="o", - byte_arrays=True) - def GetBuddyByPublicKey(self, key): - buddy = self._buddies_by_pubkey.get(key) - if buddy is not None: - if buddy.props.valid: - return buddy.object_path() - keyid = pubkey_to_keyid(key) - buddy = self._buddies.get('keyid/' + keyid) - if buddy is not None: - if buddy.props.valid: - return buddy.object_path() - raise NotFoundError("The buddy was not found.") - - @dbus.service.method(_PRESENCE_INTERFACE, in_signature='sou', - out_signature='o') - def GetBuddyByTelepathyHandle(self, tp_conn_name, tp_conn_path, handle): - """Get the buddy corresponding to a Telepathy handle. - - :Parameters: - `tp_conn_name` : str - The well-known bus name of a Telepathy connection - `tp_conn_path` : dbus.ObjectPath - The object path of the Telepathy connection - `handle` : int or long - The handle of a Telepathy contact on that connection, - of type HANDLE_TYPE_CONTACT. This may not be a - channel-specific handle. - :Returns: the object path of a Buddy - :Raises NotFoundError: if the buddy is not found. - """ - for tp, handles in self._handles_buddies.iteritems(): - conn = tp.get_connection() - if conn is None: - continue - if (conn.service_name == tp_conn_name - and conn.object_path == tp_conn_path): - buddy = handles.get(handle) - if buddy is not None and buddy.props.valid: - return buddy.object_path() - # either the handle is invalid, or we don't have a Buddy - # object for that buddy because we don't have all their - # details yet - raise NotFoundError("The buddy %u was not found on the " - "connection to %s:%s" - % (handle, tp_conn_name, tp_conn_path)) - raise NotFoundError("The buddy %u was not found: we have no " - "connection to %s:%s" % (handle, tp_conn_name, - tp_conn_path)) - - def map_handles_to_buddies(self, tp, tp_chan, handles, create=True): - """ - - :Parameters: - `tp` : Telepathy plugin - The server or link-local plugin - `tp_chan` : telepathy.client.Channel or None - If not None, the channel in which these handles are - channel-specific - `handles` : iterable over int or long - The handles to be mapped to Buddy objects - `create` : bool - If true (default), if a corresponding `Buddy` object is not - found, create one. - :Returns: - A dict mapping handles from `handles` to `Buddy` objects. - If `create` is true, the dict's keys will be exactly the - items of `handles` in some order. If `create` is false, - the dict will contain no entry for handles for which no - `Buddy` is already available. - :Raises LookupError: if `tp` is not a plugin attached to this PS. - """ - handle_to_buddy = self._handles_buddies[tp] - - ret = {} - missing = [] - for handle in handles: - buddy = handle_to_buddy.get(handle) - if buddy is None: - missing.append(handle) - else: - ret[handle] = buddy - - if missing and create: - handle_to_objid = tp.identify_contacts(tp_chan, missing) - for handle, objid in handle_to_objid.iteritems(): - buddy = self.get_buddy(objid) - ret[handle] = buddy - if tp_chan is None: - handle_to_buddy[handle] = buddy - return ret - - @dbus.service.method(_PRESENCE_INTERFACE, - in_signature='', out_signature="o") - def GetOwner(self): - if not self._owner: - raise NotFoundError("The owner was not found.") - else: - return self._owner.object_path() - - @dbus.service.method(_PRESENCE_INTERFACE, in_signature="sssa{sv}", - out_signature="o", async_callbacks=('async_cb', 'async_err_cb')) - def ShareActivity(self, actid, atype, name, properties, async_cb, - async_err_cb): - self._share_activity(actid, atype, name, properties, - (async_cb, async_err_cb)) - - @dbus.service.method(_PRESENCE_INTERFACE, - in_signature='', out_signature="so") - def GetPreferredConnection(self): - conn = self._server_plugin.get_connection() - return str(conn.service_name), conn.object_path - - def cleanup(self): - for tp in self._handles_buddies: - tp.cleanup() - - def _share_activity(self, actid, atype, name, properties, callbacks): - objid = self._get_next_object_id() - # FIXME check which tp client we should use to share the activity - color = self._owner.props.color - activity = Activity(self._session_bus, objid, self, - self._server_plugin, id=actid, type=atype, - name=name, color=color, local=True) - activity.connect("validity-changed", - self._activity_validity_changed_cb) - self._activities[actid] = activity - activity._share(callbacks, self._owner) - - # local activities are valid at creation by definition, but we can't - # connect to the activity's validity-changed signal until its already - # issued the signal, which happens in the activity's constructor - # for local activities. - self._activity_validity_changed_cb(activity, activity.props.valid) - - def _activity_validity_changed_cb(self, activity, valid): - if valid: - self.ActivityAppeared(activity.object_path()) - _logger.debug("New Activity: %s (%s)", activity.props.name, - activity.props.id) - else: - self.ActivityDisappeared(activity.object_path()) - _logger.debug("Activity disappeared: %s (%s)", activity.props.name, - activity.props.id) - - def _activity_properties_changed(self, tp, act_id, props): - activity = self._activities.get(act_id) - if activity: - activity.set_properties(props) - - -def main(test_num=0, randomize=False): - loop = gobject.MainLoop() - dbus_mainloop_wrapper = DBusGMainLoop(set_as_default=True) - - if test_num > 0: - from pstest import TestPresenceService - ps = TestPresenceService(test_num, randomize) - else: - ps = PresenceService() - - try: - loop.run() - except KeyboardInterrupt: - ps.cleanup() - _logger.debug('Ctrl+C pressed, exiting...') - -if __name__ == "__main__": - main() diff --git a/services/presence/pstest.py b/services/presence/pstest.py deleted file mode 100644 index 7715fd3..0000000 --- a/services/presence/pstest.py +++ /dev/null @@ -1,317 +0,0 @@ -# Copyright (C) 2007, Red Hat, Inc. -# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/> -# -# 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 logging -import os -import random -from ConfigParser import ConfigParser, NoOptionError - -import gobject - -from sugar import env, util - -from buddy import GenericOwner, _PROP_NICK, _PROP_CURACT, _PROP_COLOR -from presenceservice import PresenceService -from psutils import pubkey_to_keyid - - -_logger = logging.getLogger('s-p-s.pstest') - - -class TestOwner(GenericOwner): - """Class representing the owner of the machine. This test owner - changes random attributes periodically.""" - - __gtype_name__ = "TestOwner" - - def __init__(self, ps, bus, test_num, randomize): - self._cp = ConfigParser() - self._section = "Info" - self._test_activities = [] - self._test_cur_act = "" - self._change_timeout = 0 - - self._cfg_file = os.path.join(env.get_profile_path(), 'test-buddy-%d' % test_num) - - (pubkey, privkey, registered) = self._load_config() - if not pubkey or not len(pubkey) or not privkey or not len(privkey): - (pubkey, privkey) = _get_new_keypair(test_num) - - if not pubkey or not privkey: - raise RuntimeError("Couldn't get or create test buddy keypair") - - self._save_config(pubkey, privkey, registered) - privkey_hash = util.printable_hash(util._sha_data(privkey)) - - nick = _get_random_name() - from sugar.graphics import xocolor - color = xocolor.XoColor().to_string() - icon = _get_random_image() - - _logger.debug("pubkey is %s" % pubkey) - GenericOwner.__init__(self, ps, bus, - 'keyid/' + pubkey_to_keyid(pubkey), - key=pubkey, nick=nick, color=color, icon=icon, - registered=registered, key_hash=privkey_hash) - - # Only do the random stuff if randomize is true - if randomize: - self._ps.connect('connection-status', self._ps_connection_status_cb) - - def _share_reply_cb(self, actid, object_path): - activity = self._ps.internal_get_activity(actid) - if not activity or not object_path: - _logger.debug("Couldn't find activity %s even though it was shared." % actid) - return - _logger.debug("Shared activity %s (%s)." % (actid, activity.props.name)) - self._test_activities.append(activity) - - def _share_error_cb(self, actid, err): - _logger.debug("Error sharing activity %s: %s" % (actid, str(err))) - - def _ps_connection_status_cb(self, ps, connected): - if not connected: - return - - if not len(self._test_activities): - # Share some activities - actid = util.unique_id("Activity 1") - callbacks = (lambda *args: self._share_reply_cb(actid, *args), - lambda *args: self._share_error_cb(actid, *args)) - atype = "org.laptop.WebActivity" - properties = {"foo": "bar"} - self._ps._share_activity(actid, atype, "Wembley Stadium", properties, callbacks) - - actid2 = util.unique_id("Activity 2") - callbacks = (lambda *args: self._share_reply_cb(actid2, *args), - lambda *args: self._share_error_cb(actid2, *args)) - atype = "org.laptop.WebActivity" - properties = {"baz": "bar"} - self._ps._share_activity(actid2, atype, "Maine Road", properties, callbacks) - - # Change a random property ever 10 seconds - if self._change_timeout == 0: - self._change_timeout = gobject.timeout_add(10000, self._update_something) - - def set_registered(self, value): - if value: - self._registered = True - - def _load_config(self): - if not os.path.exists(self._cfg_file): - return (None, None, False) - if not self._cp.read([self._cfg_file]): - return (None, None, False) - if not self._cp.has_section(self._section): - return (None, None, False) - - try: - pubkey = self._cp.get(self._section, "pubkey") - privkey = self._cp.get(self._section, "privkey") - registered = self._cp.get(self._section, "registered") - return (pubkey, privkey, registered) - except NoOptionError: - pass - - return (None, None, False) - - def _save_config(self, pubkey, privkey, registered): - # Save config again - if not self._cp.has_section(self._section): - self._cp.add_section(self._section) - self._cp.set(self._section, "pubkey", pubkey) - self._cp.set(self._section, "privkey", privkey) - self._cp.set(self._section, "registered", registered) - f = open(self._cfg_file, 'w') - self._cp.write(f) - f.close() - - def _update_something(self): - it = random.randint(0, 10000) % 4 - if it == 0: - self.props.icon = _get_random_image() - elif it == 1: - from sugar.graphics import xocolor - props = {_PROP_COLOR: xocolor.XoColor().to_string()} - self.set_properties(props) - elif it == 2: - props = {_PROP_NICK: _get_random_name()} - self.set_properties(props) - elif it == 3: - actid = "" - idx = random.randint(0, len(self._test_activities)) - # if idx == len(self._test_activites), it means no current - # activity - if idx < len(self._test_activities): - activity = self._test_activities[idx] - actid = activity.props.id - props = {_PROP_CURACT: actid} - self.set_properties(props) - return True - - -class TestPresenceService(PresenceService): - - def __init__(self, test_num=0, randomize=False): - self.__test_num = test_num - self.__randomize = randomize - PresenceService.__init__(self) - - def _create_owner(self): - return TestOwner(self, self._session_bus, - self.__test_num, self.__randomize) - - def internal_get_activity(self, actid): - return self._activities.get(actid, None) - - -def _extract_public_key(keyfile): - try: - f = open(keyfile, "r") - lines = f.readlines() - f.close() - except IOError, e: - _logger.error("Error reading public key: %s" % e) - return None - - # Extract the public key - magic = "ssh-dss " - key = "" - for l in lines: - l = l.strip() - if not l.startswith(magic): - continue - key = l[len(magic):] - break - if not len(key): - _logger.error("Error parsing public key.") - return None - return key - -def _extract_private_key(keyfile): - """Get a private key from a private key file""" - # Extract the private key - try: - f = open(keyfile, "r") - lines = f.readlines() - f.close() - except IOError, e: - _logger.error("Error reading private key: %s" % e) - return None - - 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): - _logger.error("Error parsing private key.") - return None - return key - -def _get_new_keypair(num): - """Retrieve a public/private key pair for testing""" - # Generate keypair - privkeyfile = os.path.join("/tmp", "test%d.key" % num) - pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num) - - # force-remove key files if they exist to ssh-keygen doesn't - # start asking questions - try: - os.remove(pubkeyfile) - os.remove(privkeyfile) - except OSError: - pass - - cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % privkeyfile - import commands - print "Generating new keypair..." - (s, o) = commands.getstatusoutput(cmd) - print "Done." - pubkey = privkey = None - if s != 0: - _logger.error("Could not generate key pair: %d (%s)" % (s, o)) - else: - pubkey = _extract_public_key(pubkeyfile) - privkey = _extract_private_key(privkeyfile) - - try: - os.remove(pubkeyfile) - os.remove(privkeyfile) - except OSError: - pass - return (pubkey, privkey) - -def _get_random_name(): - """Produce random names for testing""" - names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"] - return names[random.randint(0, len(names) - 1)] - -def _get_random_image(): - """Produce a random image for display""" - import cairo, math, gtk - - def rand(): - return random.random() - - SIZE = 200 - - s = cairo.ImageSurface(cairo.FORMAT_ARGB32, SIZE, SIZE) - cr = cairo.Context(s) - - # background gradient - cr.save() - g = cairo.LinearGradient(0, 0, 1, 1) - g.add_color_stop_rgba(1, rand(), rand(), rand(), rand()) - g.add_color_stop_rgba(0, rand(), rand(), rand(), rand()) - cr.set_source(g) - cr.rectangle(0, 0, SIZE, SIZE); - cr.fill() - cr.restore() - - # random path - cr.set_line_width(10 * rand() + 5) - cr.move_to(SIZE * rand(), SIZE * rand()) - cr.line_to(SIZE * rand(), SIZE * rand()) - cr.rel_line_to(SIZE * rand() * -1, 0) - cr.close_path() - cr.stroke() - - # a circle - cr.set_source_rgba(rand(), rand(), rand(), rand()) - cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi) - cr.fill() - - # another circle - cr.set_source_rgba(rand(), rand(), rand(), rand()) - cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi) - cr.fill() - - def img_convert_func(buf, data): - data[0] += buf - return True - - data = [""] - pixbuf = gtk.gdk.pixbuf_new_from_data(s.get_data(), gtk.gdk.COLORSPACE_RGB, - True, 8, s.get_width(), s.get_height(), s.get_stride()) - pixbuf.save_to_callback(img_convert_func, "jpeg", {"quality": "90"}, data) - del pixbuf - - return str(data[0]) diff --git a/services/presence/psutils.py b/services/presence/psutils.py deleted file mode 100644 index 25b24b9..0000000 --- a/services/presence/psutils.py +++ /dev/null @@ -1,259 +0,0 @@ -# Copyright (C) 2007, Red Hat, Inc. -# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/> -# -# 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 logging -from string import ascii_letters, digits -try: - from hashlib import sha1 -except ImportError: - # Python < 2.5 - from sha import new as sha1 - -import dbus -import gobject - - -_logger = logging.getLogger('s-p-s.psutils') - -_ASCII_ALNUM = ascii_letters + digits - - -def pubkey_to_keyid(key): - """Return the key ID for the given public key. This is currently its SHA-1 - in hex. - - :Parameters: - `key` : str - The public key as a Base64 string - :Returns: - The key ID as a string of hex digits - """ - return sha1(key).hexdigest() - - -def escape_identifier(identifier): - """Escape the given string to be a valid D-Bus object path or service - name component, using a reversible encoding to ensure uniqueness. - - The reversible encoding is as follows: - - * The empty string becomes '_' - * Otherwise, each non-alphanumeric character is replaced by '_' plus - two lower-case hex digits; the same replacement is carried out on - the first character, if it's a digit - """ - # '' -> '_' - if not identifier: - return '_' - - # A bit of a fast path for strings which are already OK. - # We deliberately omit '_' because, for reversibility, that must also - # be escaped. - if (identifier.strip(_ASCII_ALNUM) == '' and - identifier[0] in ascii_letters): - return identifier - - # The first character may not be a digit - if identifier[0] not in ascii_letters: - ret = ['_%02x' % ord(identifier[0])] - else: - ret = [identifier[0]] - - # Subsequent characters may be digits or ASCII letters - for c in identifier[1:]: - if c in _ASCII_ALNUM: - ret.append(c) - else: - ret.append('_%02x' % ord(c)) - - return ''.join(ret) - - -NM_SERVICE = 'org.freedesktop.NetworkManager' -NM_IFACE = 'org.freedesktop.NetworkManager' -NM_IFACE_DEVICES = 'org.freedesktop.NetworkManager.Devices' -NM_PATH = '/org/freedesktop/NetworkManager' - -_ip4am = None - -class IP4AddressMonitor(gobject.GObject): - """This class, and direct buddy IPv4 address access, will go away quite soon""" - - __gsignals__ = { - 'address-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) - } - - __gproperties__ = { - 'address' : (str, None, None, None, gobject.PARAM_READABLE) - } - - def get_instance(): - """Retrieve (or create) the IP4Address monitor singleton instance""" - global _ip4am - if not _ip4am: - _ip4am = IP4AddressMonitor() - return _ip4am - get_instance = staticmethod(get_instance) - - def __init__(self): - gobject.GObject.__init__(self) - self._nm_present = False - self._nm_has_been_present = False - self._matches = [] - self._addr = None - self._nm_obj = None - - sys_bus = dbus.SystemBus() - self._watch = sys_bus.watch_name_owner(NM_SERVICE, self._nm_owner_cb) - if not sys_bus.name_has_owner(NM_SERVICE): - addr = self._get_address_fallback() - self._update_address(addr) - - def do_get_property(self, pspec): - if pspec.name == "address": - return self._addr - - def _update_address(self, new_addr): - if new_addr == "0.0.0.0": - new_addr = None - if new_addr == self._addr: - return - - self._addr = new_addr - _logger.debug("IP4 address now '%s'" % new_addr) - self.emit('address-changed', new_addr) - - def _connect_to_nm(self): - """Connect to NM device state signals to tell when the IPv4 address changes""" - try: - sys_bus = dbus.SystemBus() - proxy = sys_bus.get_object(NM_SERVICE, NM_PATH) - self._nm_obj = dbus.Interface(proxy, NM_IFACE) - except dbus.DBusException, err: - _logger.debug("Error finding NetworkManager: %s" % err) - self._nm_present = False - return - - sys_bus = dbus.SystemBus() - match = sys_bus.add_signal_receiver(self._nm_device_active_cb, - signal_name="DeviceNowActive", - dbus_interface=NM_IFACE) - self._matches.append(match) - - match = sys_bus.add_signal_receiver(self._nm_device_no_longer_active_cb, - signal_name="DeviceNoLongerActive", - dbus_interface=NM_IFACE, - bus_name=NM_SERVICE) - self._matches.append(match) - - match = sys_bus.add_signal_receiver(self._nm_state_change_cb, - signal_name="StateChange", - dbus_interface=NM_IFACE, - bus_name=NM_SERVICE) - self._matches.append(match) - - state = self._nm_obj.state() - if state == 3: # NM_STATE_CONNECTED - self._query_devices() - - def _device_properties_cb(self, *props): - active = props[4] - if not active: - return - act_stage = props[5] - # HACK: OLPC NM has an extra stage, so activated == 8 on OLPC - # but 7 everywhere else - if act_stage != 8 and act_stage != 7: - # not activated - return - self._update_address(props[6]) - - def _device_properties_error_cb(self, err): - _logger.debug("Error querying device properties: %s" % err) - - def _query_device_properties(self, device): - sys_bus = dbus.SystemBus() - proxy = sys_bus.get_object(NM_SERVICE, device) - dev = dbus.Interface(proxy, NM_IFACE_DEVICES) - dev.getProperties(reply_handler=self._device_properties_cb, - error_handler=self._device_properties_error_cb) - - def _get_devices_cb(self, ops): - """Query each device's properties""" - for op in ops: - self._query_device_properties(op) - - def _get_devices_error_cb(self, err): - _logger.debug("Error getting NetworkManager devices: %s" % err) - - def _query_devices(self): - """Query NM for a list of network devices""" - self._nm_obj.getDevices(reply_handler=self._get_devices_cb, - error_handler=self._get_devices_error_cb) - - def _nm_device_active_cb(self, device, ssid=None): - self._query_device_properties(device) - - def _nm_device_no_longer_active_cb(self, device): - self._update_address(None) - - def _nm_state_change_cb(self, new_state): - if new_state == 4: # NM_STATE_DISCONNECTED - self._update_address(None) - - def _nm_owner_cb(self, unique_name): - """Clear state when NM goes away""" - if unique_name == '': - # NM went away, or isn't there at all - self._nm_present = False - for match in self._matches: - match.remove() - self._matches = [] - if self._nm_has_been_present: - self._update_address(None) - else: - addr = self._get_address_fallback() - self._update_address(addr) - elif not self._nm_present: - # NM started up - self._nm_present = True - self._nm_has_been_present = True - self._connect_to_nm() - - def _get_iface_address(self, iface): - import socket - import fcntl - import struct - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - fd = s.fileno() - SIOCGIFADDR = 0x8915 - addr = fcntl.ioctl(fd, SIOCGIFADDR, struct.pack('256s', iface[:15]))[20:24] - s.close() - return socket.inet_ntoa(addr) - - def _get_address_fallback(self): - import commands - (s, o) = commands.getstatusoutput("/sbin/route -n") - if s != 0: - return - for line in o.split('\n'): - fields = line.split(" ") - if fields[0] == "0.0.0.0": - iface = fields[len(fields) - 1] - return self._get_iface_address(iface) - return None diff --git a/services/presence/server_plugin.py b/services/presence/server_plugin.py deleted file mode 100644 index 548b41f..0000000 --- a/services/presence/server_plugin.py +++ /dev/null @@ -1,1171 +0,0 @@ -"""Telepathy-python presence server interface/implementation plugin""" -# 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 - -# Standard library -import logging -import os -import sys -from string import hexdigits -try: - # Python >= 2.5 - from hashlib import md5 -except ImportError: - from md5 import new as md5 - -# Other libraries -import dbus -import gobject -import gtk -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, - CHANNEL_TYPE_TEXT, CHANNEL_TYPE_STREAMED_MEDIA, PROPERTIES_INTERFACE) -from telepathy.constants import (HANDLE_TYPE_CONTACT, - HANDLE_TYPE_LIST, HANDLE_TYPE_CONTACT, HANDLE_TYPE_ROOM, - CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, - CONNECTION_STATUS_CONNECTING, - CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED, - CONNECTION_STATUS_REASON_NONE_SPECIFIED, - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, - PROPERTY_FLAG_WRITE) -from sugar import util - -# Presence Service local modules -from buddyiconcache import BuddyIconCache -import psutils - - -CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' -CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties' - -_PROTOCOL = "jabber" -_OBJ_PATH_PREFIX = "/org/freedesktop/Telepathy/Connection/gabble/jabber/" - -_logger = logging.getLogger('s-p-s.server_plugin') - -_RECONNECT_TIMEOUT = 5000 - -def _buddy_icon_save_cb(buf, data): - data[0] += buf - return True - -def _get_buddy_icon_at_size(icon, maxw, maxh, maxsize): - loader = gtk.gdk.PixbufLoader() - loader.write(icon) - loader.close() - unscaled_pixbuf = loader.get_pixbuf() - del loader - - pixbuf = unscaled_pixbuf.scale_simple(maxw, maxh, gtk.gdk.INTERP_BILINEAR) - del unscaled_pixbuf - - data = [""] - quality = 90 - img_size = maxsize + 1 - while img_size > maxsize: - del data - data = [""] - pixbuf.save_to_callback(_buddy_icon_save_cb, "jpeg", - {"quality":"%d" % quality}, data) - quality -= 10 - img_size = len(data[0]) - del pixbuf - - if img_size > maxsize: - del data - raise RuntimeError("could not size image less than %d bytes" % maxsize) - - return str(data[0]) - - -class ServerPlugin(gobject.GObject): - """Telepathy-python-based presence server interface - - The ServerPlugin instance translates network events from - Telepathy Python into GObject events. It provides direct - python calls to perform the required network operations - to implement the PresenceService. - """ - __gsignals__ = { - 'contact-online': - # Contact has come online and we've discovered all their buddy - # properties. - # args: - # contact identification (based on key ID or JID): str - # contact handle: int or long - # dict {name: str => property: object} - (gobject.SIGNAL_RUN_FIRST, None, [str, object, object]), - 'contact-offline': - # Contact has gone offline. - # args: contact handle - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'status': - # Connection status changed. - # args: status, reason as for Telepathy StatusChanged - (gobject.SIGNAL_RUN_FIRST, None, [int, int]), - 'avatar-updated': - # Contact's avatar has changed - # args: contact handle: int; icon data: str - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'buddy-properties-changed': - # OLPC buddy properties changed; as for PropertiesChanged - # args: - # contact handle: int - # properties: dict {name: str => property: object} - # FIXME: are these all the properties or just those that changed? - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'buddy-activities-changed': - # OLPC activities changed - # args: - # contact handle: int - # activity IDs: list of str - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'activity-invitation': - # We were invited to join an activity - # args: activity ID: str - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'private-invitation': - # We were invited to join a chat or a media call - # args: channel object path - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'activity-properties-changed': - # An activity's properties changed; as for - # ActivityPropertiesChanged - # args: activity ID: str; properties: dict { str => object } - # FIXME: are these all the properties or just those that changed? - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'activity-shared': - # share_activity() succeeded - # args: - # activity ID: str - # channel: telepathy.client.Channel, or None on failure - # error: None, or Exception on failure - # userdata as passed to share_activity - (gobject.SIGNAL_RUN_FIRST, None, [object, object, object, object]), - 'activity-joined': - # join_activity() succeeded - # args: as for activity-shared - (gobject.SIGNAL_RUN_FIRST, None, [object, object, object, object]), - } - - def __init__(self, registry, owner): - """Initialize the ServerPlugin instance - - registry -- telepathy.client.ManagerRegistry from the - PresenceService, used to find the "gabble" connection - manager in this case... - owner -- presence.buddy.GenericOwner instance (normally a - presence.buddy.ShellOwner instance) - """ - gobject.GObject.__init__(self) - - self._conn = None - self._icon_cache = BuddyIconCache() - - self._registry = registry - self._online_contacts = {} # handle -> jid - - # activity id -> handle - self._activities = {} - # (activity_id, handle of the activity channel) - self._joined_activities = [] - - self._owner = owner - self._owner.connect("property-changed", - self._owner_property_changed_cb) - self._owner.connect("icon-changed", self._owner_icon_changed_cb) - self.self_handle = None - - self._account = self._get_account_info() - self._conn_status = CONNECTION_STATUS_DISCONNECTED - self._reconnect_id = 0 - - # Monitor IPv4 address as an indicator of the network connection - self._ip4am = psutils.IP4AddressMonitor.get_instance() - self._ip4am.connect('address-changed', self._ip4_address_changed_cb) - - self._publish_channel = None - self._subscribe_channel = None - self._subscribe_members = set() - self._subscribe_local_pending = set() - self._subscribe_remote_pending = set() - - def _ip4_address_changed_cb(self, ip4am, address): - _logger.debug("::: IP4 address now %s", address) - if address: - _logger.debug("::: valid IP4 address, conn_status %s", - self._conn_status) - if self._conn_status == CONNECTION_STATUS_DISCONNECTED: - _logger.debug("::: will connect") - self.start() - else: - _logger.debug("::: invalid IP4 address, will disconnect") - self.cleanup() - - def _owner_property_changed_cb(self, owner, properties): - """Local user's configuration properties have changed - - owner -- the Buddy object for the local user - properties -- set of updated properties - - calls: - - _set_self_current_activity current-activity - _set_self_alias nick - _set_self_olpc_properties color - - depending on which properties are present in the - set of properties. - """ - _logger.debug("Owner properties changed: %s", properties) - - if properties.has_key("current-activity"): - self._set_self_current_activity() - - if properties.has_key("nick"): - self._set_self_alias() - # Hack; send twice to make sure the server gets it - gobject.timeout_add(1000, self._set_self_alias) - - if properties.has_key("color") or properties.has_key("ip4-address"): - if self._conn_status == CONNECTION_STATUS_CONNECTED: - self._set_self_olpc_properties() - - def _owner_icon_changed_cb(self, owner, icon): - """Owner has changed their icon, forward to network""" - _logger.debug("Owner icon changed to size %d", len(str(icon))) - self._set_self_avatar(icon) - - def _get_account_info(self): - """Retrieve metadata dictionary describing this account - - returns dictionary with: - - server : server url from owner - account : printable-ssh-key-hash@server - password : ssh-key-hash - register : whether to register (i.e. whether not yet - registered) - """ - account_info = {} - - account_info['server'] = self._owner.get_server() - - khash = psutils.pubkey_to_keyid(self._owner.props.key) - account_info['account'] = "%s@%s" % (khash, account_info['server']) - - account_info['password'] = self._owner.get_key_hash() - account_info['register'] = not self._owner.get_registered() - - print "ACCT: %s" % account_info - return account_info - - def _find_existing_connection(self): - """Try to find an existing Telepathy connection to this server - - filters the set of connections from - telepathy.client.Connection.get_connections - to find a connection using our protocol with the - "self handle" of that connection being a handle - which matches our account (see _get_account_info) - - returns connection or None - """ - our_name = self._account['account'] - - # 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(_OBJ_PATH_PREFIX): - continue - if item[CONN_INTERFACE].GetProtocol() != _PROTOCOL: - continue - if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED: - test_handle = item[CONN_INTERFACE].RequestHandles( - HANDLE_TYPE_CONTACT, [our_name])[0] - if item[CONN_INTERFACE].GetSelfHandle() != test_handle: - continue - return item - return None - - def get_connection(self): - """Retrieve our telepathy.client.Connection object""" - return self._conn - - def _init_connection(self): - """Set up our connection - - if there is no existing connection - (_find_existing_connection returns None) - produce a new connection with our protocol for our - account. - - if there is an existing connection, reuse it by - registering for various of events on it. - """ - conn = self._find_existing_connection() - if not conn: - acct = self._account.copy() - - # Create a new connection - gabble_mgr = self._registry.GetManager('gabble') - name, path = gabble_mgr[CONN_MGR_INTERFACE].RequestConnection( - _PROTOCOL, acct) - conn = Connection(name, path) - del acct - - conn[CONN_INTERFACE].connect_to_signal('StatusChanged', - self._status_changed_cb) - conn[CONN_INTERFACE].connect_to_signal('NewChannel', - self._new_channel_cb) - - # hack - conn._valid_interfaces.add(CONN_INTERFACE_PRESENCE) - conn._valid_interfaces.add(CONN_INTERFACE_BUDDY_INFO) - conn._valid_interfaces.add(CONN_INTERFACE_ACTIVITY_PROPERTIES) - conn._valid_interfaces.add(CONN_INTERFACE_AVATARS) - conn._valid_interfaces.add(CONN_INTERFACE_ALIASING) - - conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate', - self._presence_update_cb) - - self._conn = conn - status = self._conn[CONN_INTERFACE].GetStatus() - - if status == CONNECTION_STATUS_DISCONNECTED: - def connect_reply(): - _logger.debug('Connect() succeeded') - def connect_error(e): - _logger.debug('Connect() failed: %s', e) - if not self._reconnect_id: - self._reconnect_id = gobject.timeout_add(_RECONNECT_TIMEOUT, - self._reconnect_cb) - - self._conn[CONN_INTERFACE].Connect(reply_handler=connect_reply, - error_handler=connect_error) - - self._handle_connection_status_change(status, - CONNECTION_STATUS_REASON_NONE_SPECIFIED) - - def _connected_cb(self): - """Callback on successful connection to a server - """ - - if self._account['register']: - # we successfully register this account - self._owner.set_registered(True) - - # request both handles at the same time to reduce round-trips - pub_handle, sub_handle = self._conn[CONN_INTERFACE].RequestHandles( - HANDLE_TYPE_LIST, ['publish', 'subscribe']) - - # the group of contacts who may receive your presence - publish = self._conn.request_channel(CHANNEL_TYPE_CONTACT_LIST, - HANDLE_TYPE_LIST, pub_handle, True) - self._publish_channel = publish - publish[CHANNEL_INTERFACE_GROUP].connect_to_signal('MembersChanged', - self._publish_members_changed_cb) - publish_handles, local_pending, remote_pending = \ - publish[CHANNEL_INTERFACE_GROUP].GetAllMembers() - - # the group of contacts for whom you wish to receive presence - subscribe = self._conn.request_channel(CHANNEL_TYPE_CONTACT_LIST, - HANDLE_TYPE_LIST, sub_handle, True) - self._subscribe_channel = subscribe - subscribe[CHANNEL_INTERFACE_GROUP].connect_to_signal('MembersChanged', - self._subscribe_members_changed_cb) - subscribe_handles, subscribe_lp, subscribe_rp = \ - subscribe[CHANNEL_INTERFACE_GROUP].GetAllMembers() - self._subscribe_members = set(subscribe_handles) - self._subscribe_local_pending = set(subscribe_lp) - self._subscribe_remote_pending = set(subscribe_rp) - - if local_pending: - # accept pending subscriptions - publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '') - - self.self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() - self._online_contacts[self.self_handle] = self._account['account'] - - # request subscriptions from people subscribed to us if we're not - # subscribed to them - not_subscribed = list(set(publish_handles) - set(subscribe_handles)) - subscribe[CHANNEL_INTERFACE_GROUP].AddMembers(not_subscribed, '') - - if CONN_INTERFACE_BUDDY_INFO not in self._conn.get_valid_interfaces(): - _logger.debug('OLPC information not available') - return False - - self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal( - 'PropertiesChanged', self._buddy_properties_changed_cb) - self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal( - 'ActivitiesChanged', self._buddy_activities_changed_cb) - self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal( - 'CurrentActivityChanged', - self._buddy_current_activity_changed_cb) - - self._conn[CONN_INTERFACE_AVATARS].connect_to_signal('AvatarUpdated', - self._avatar_updated_cb) - self._conn[CONN_INTERFACE_ALIASING].connect_to_signal('AliasesChanged', - self._alias_changed_cb) - self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].connect_to_signal( - 'ActivityPropertiesChanged', - self._activity_properties_changed_cb) - - # Set initial buddy properties, avatar, and activities - self._set_self_olpc_properties() - self._set_self_alias() - # Hack; send twice to make sure the server gets it - gobject.timeout_add(1000, self._set_self_alias) - self._set_self_activities() - self._set_self_current_activity() - self._set_self_avatar() - - # Request presence for everyone we're subscribed to - self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles) - return True - - def _set_self_avatar_cb(self, token): - self._icon_cache.set_avatar(hash, token) - - def _set_self_avatar(self, icon_data=None): - if not icon_data: - icon_data = self._owner.props.icon - - m = md5() - m.update(icon_data) - hash = m.hexdigest() - - self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() - token = self._conn[CONN_INTERFACE_AVATARS].GetAvatarTokens( - [self_handle])[0] - - if self._icon_cache.check_avatar(hash, token): - # avatar is up to date - return - - types, minw, minh, maxw, maxh, maxsize = \ - self._conn[CONN_INTERFACE_AVATARS].GetAvatarRequirements() - if not "image/jpeg" in types: - _logger.debug("server does not accept JPEG format avatars.") - return - - img_data = _get_buddy_icon_at_size(icon_data, min(maxw, 96), - min(maxh, 96), maxsize) - self._conn[CONN_INTERFACE_AVATARS].SetAvatar(img_data, "image/jpeg", - reply_handler=self._set_self_avatar_cb, - error_handler=lambda e: self._log_error_cb("setting avatar", e)) - - def _join_activity_channel_props_set_cb(self, activity_id, signal, handle, - channel, userdata): - self._joined_activities.append((activity_id, handle)) - self._set_self_activities() - self.emit(signal, activity_id, channel, None, userdata) - - def _join_activity_channel_props_listed_cb(self, activity_id, signal, - handle, channel, userdata, - props, prop_specs): - - props_to_set = [] - for ident, name, sig, flags in prop_specs: - value = props.pop(name, None) - if value is not None: - if flags & PROPERTY_FLAG_WRITE: - props_to_set.append((ident, value)) - # FIXME: else error, but only if we're creating the room? - # FIXME: if props is nonempty, then we want to set props that aren't - # supported here - raise an error? - - if props_to_set: - channel[PROPERTIES_INTERFACE].SetProperties(props_to_set, - reply_handler=lambda: self._join_activity_channel_props_set_cb( - activity_id, signal, handle, channel, userdata), - error_handler=lambda e: self._join_error_cb( - activity_id, signal, userdata, - 'SetProperties(%r)' % props_to_set, e)) - else: - self._join_activity_channel_props_set_cb(activity_id, signal, - handle, channel, userdata) - - def _join_activity_create_channel_cb(self, activity_id, signal, handle, - userdata, chan_path): - channel = Channel(self._conn.service_name, chan_path) - props = { - 'anonymous': False, # otherwise buddy resolution breaks - 'invite-only': False, # XXX: should be True in future - #'name': ... # XXX: set from activity name? - 'persistent': False, # vanish when there are no members - 'private': False, # XXX: should be True unless public - } - channel[PROPERTIES_INTERFACE].ListProperties( - reply_handler=lambda prop_specs: self._join_activity_channel_props_listed_cb( - activity_id, signal, handle, channel, userdata, props, prop_specs), - error_handler=lambda e: self._join_error_cb( - activity_id, signal, userdata, 'ListProperties', e)) - - def _join_activity_get_channel_cb(self, activity_id, signal, userdata, - handles): - if not self._activities.has_key(activity_id): - self._activities[activity_id] = handles[0] - - if (activity_id, handles[0]) in self._joined_activities: - e = RuntimeError("Already joined activity %s" % activity_id) - _logger.debug('%s', e) - self.emit(signal, activity_id, None, e, userdata) - return - - self._conn[CONN_INTERFACE].RequestChannel(CHANNEL_TYPE_TEXT, - HANDLE_TYPE_ROOM, handles[0], True, - reply_handler=lambda *args: self._join_activity_create_channel_cb( - activity_id, signal, handles[0], userdata, *args), - error_handler=lambda e: self._join_error_cb(activity_id, signal, - userdata, 'RequestChannel(TEXT, ROOM, %r, True)' % handles[0], - e)) - - def _join_error_cb(self, activity_id, signal, userdata, where, err): - e = Exception("Error joining/sharing activity %s: (%s): %s" - % (activity_id, where, err)) - _logger.debug('%s', e) - self.emit(signal, activity_id, None, e, userdata) - - def _internal_join_activity(self, activity_id, signal, userdata): - handle = self._activities.get(activity_id) - if not handle: - # FIXME: figure out why the server can't figure this out itself - room_jid = activity_id + "@conference." + self._account["server"] - self._conn[CONN_INTERFACE].RequestHandles(HANDLE_TYPE_ROOM, - [room_jid], - reply_handler=lambda *args: self._join_activity_get_channel_cb( - activity_id, signal, userdata, *args), - error_handler=lambda e: self._join_error_cb(activity_id, - signal, userdata, 'RequestHandles([%u])' % room_jid, - e)) - else: - self._join_activity_get_channel_cb(activity_id, signal, userdata, - [handle]) - - def share_activity(self, activity_id, userdata): - """Share activity with the network - - activity_id -- unique ID for the activity - userdata -- opaque token to be passed in the resulting event - (id, callback, errback) normally - - Asks the Telepathy server to create a "conference" channel - for the activity or return a handle to an already created - conference channel for the activity. - """ - self._internal_join_activity(activity_id, "activity-shared", userdata) - - def join_activity(self, activity_id, userdata): - """Join an activity on the network (or locally) - - activity_id -- unique ID for the activity - userdata -- opaque token to be passed in the resulting event - (id, callback, errback) normally - - Asks the Telepathy server to create a "conference" channel - for the activity or return a handle to an already created - conference channel for the activity. - """ - self._internal_join_activity(activity_id, "activity-joined", userdata) - - def _ignore_success_cb(self): - """Ignore an event (null-operation)""" - - def _log_error_cb(self, msg, err): - """Log a message (error) at debug level with prefix msg""" - _logger.debug("Error %s: %s", msg, err) - - def _set_self_olpc_properties(self): - """Set color and key on our Telepathy server identity""" - props = {} - props['color'] = self._owner.props.color - props['key'] = dbus.ByteArray(self._owner.props.key) - addr = self._owner.props.ip4_address - if not addr: - props['ip4-address'] = "" - else: - props['ip4-address'] = addr - self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting properties", e)) - - def _set_self_alias(self): - """Forwarded to SetActivities on AliasInfo channel""" - alias = self._owner.props.nick - self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() - self._conn[CONN_INTERFACE_ALIASING].SetAliases({self_handle : alias}, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting alias", e)) - return False - - def _set_self_activities(self): - """Forward set of joined activities to network - - uses SetActivities on BuddyInfo channel - """ - self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities( - self._joined_activities, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting activities", e)) - - def _set_self_current_activity(self): - """Forward our current activity (or "") to network - - uses SetCurrentActivity on BuddyInfo channel - """ - cur_activity = self._owner.props.current_activity - cur_activity_handle = 0 - if not cur_activity: - cur_activity = "" - else: - cur_activity_handle = self._get_handle_for_activity(cur_activity) - if not cur_activity_handle: - # dont advertise a current activity that's not shared - cur_activity = "" - - _logger.debug("Setting current activity to '%s' (handle %s)", - cur_activity, cur_activity_handle) - self._conn[CONN_INTERFACE_BUDDY_INFO].SetCurrentActivity(cur_activity, - cur_activity_handle, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting current activity", e)) - - def _get_handle_for_activity(self, activity_id): - """Retrieve current handle for given activity or None""" - for (act, handle) in self._joined_activities: - if activity_id == act: - return handle - return None - - def _reconnect_cb(self): - """Attempt to reconnect to the server""" - self.start() - return False - - def _handle_connection_status_change(self, status, reason): - if status == self._conn_status: - return - - if status == CONNECTION_STATUS_CONNECTING: - self._conn_status = status - _logger.debug("status: connecting...") - elif status == CONNECTION_STATUS_CONNECTED: - if self._connected_cb(): - _logger.debug("status: connected") - self._conn_status = status - else: - self.cleanup() - _logger.debug("status: was connected, but an error occurred") - elif status == CONNECTION_STATUS_DISCONNECTED: - self.cleanup() - _logger.debug("status: disconnected (reason %r)", reason) - if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED: - # FIXME: handle connection failure; retry later? - pass - else: - # If disconnected, but still have a network connection, retry - # If disconnected and no network connection, do nothing here - # and let the IP4AddressMonitor address-changed signal handle - # reconnection - if self._ip4am.props.address and not self._reconnect_id: - self._reconnect_id = gobject.timeout_add(_RECONNECT_TIMEOUT, - self._reconnect_cb) - - self.emit('status', self._conn_status, int(reason)) - return False - - def _status_changed_cb(self, status, reason): - """Handle notification of connection-status change - - status -- CONNECTION_STATUS_* - reason -- integer code describing the reason... - """ - _logger.debug("::: connection status changed to %s", status) - self._handle_connection_status_change(status, reason) - - def start(self): - """Start up the Telepathy networking connections - - if we are already connected, query for the initial contact - information. - - if we are already connecting, do nothing - - otherwise initiate a connection and transfer control to - _connect_reply_cb or _connect_error_cb - """ - _logger.debug("Starting up...") - - if self._reconnect_id > 0: - gobject.source_remove(self._reconnect_id) - self._reconnect_id = 0 - - # Only init connection if we have a valid IP address - if self._ip4am.props.address: - _logger.debug("::: Have IP4 address %s, will connect", - self._ip4am.props.address) - self._init_connection() - else: - _logger.debug("::: No IP4 address, postponing connection") - - def cleanup(self): - """If we still have a connection, disconnect it""" - if self._conn: - try: - self._conn[CONN_INTERFACE].Disconnect() - except: - pass - self._conn = None - self._conn_status = CONNECTION_STATUS_DISCONNECTED - - for handle in self._online_contacts.keys(): - self._contact_offline(handle) - self._online_contacts = {} - self._joined_activites = [] - self._activites = {} - - if self._reconnect_id > 0: - gobject.source_remove(self._reconnect_id) - self._reconnect_id = 0 - - def _contact_offline(self, handle): - """Handle contact going offline (send message, update set)""" - if not self._online_contacts.has_key(handle): - return - if self._online_contacts[handle]: - self.emit("contact-offline", handle) - del self._online_contacts[handle] - - def _contact_online_activities_cb(self, handle, activities): - """Handle contact's activity list update""" - self._buddy_activities_changed_cb(handle, activities) - - def _contact_online_activities_error_cb(self, handle, err): - """Handle contact's activity list being unavailable""" - _logger.debug("Handle %s - Error getting activities: %s", - handle, err) - # Don't drop the buddy if we can't get their activities, for now - #self._contact_offline(handle) - - def _contact_online_aliases_cb(self, handle, props, aliases): - """Handle contact's alias being received (do further queries)""" - if not self._conn or not aliases or not len(aliases): - _logger.debug("Handle %s - No aliases", handle) - self._contact_offline(handle) - return - - props['nick'] = aliases[0] - - jid = self._conn[CONN_INTERFACE].InspectHandles(HANDLE_TYPE_CONTACT, - [handle])[0] - self._online_contacts[handle] = jid - objid = self.identify_contacts(None, [handle])[handle] - - self.emit("contact-online", objid, handle, props) - - self._conn[CONN_INTERFACE_BUDDY_INFO].GetActivities(handle, - reply_handler=lambda *args: self._contact_online_activities_cb( - handle, *args), - error_handler=lambda e: self._contact_online_activities_error_cb( - handle, e)) - - def _contact_online_aliases_error_cb(self, handle, props, retry, err): - """Handle failure to retrieve given user's alias/information""" - if retry: - _logger.debug("Handle %s - Error getting nickname (will retry):" - "%s", handle, err) - self._conn[CONN_INTERFACE_ALIASING].RequestAliases([handle], - reply_handler=lambda *args: self._contact_online_aliases_cb( - handle, props, *args), - error_handler=lambda e: self._contact_online_aliases_error_cb( - handle, props, False, e)) - else: - _logger.debug("Handle %s - Error getting nickname: %s", - handle, err) - self._contact_offline(handle) - - def _contact_online_properties_cb(self, handle, props): - """Handle failure to retrieve given user's alias/information""" - if not props.has_key('key'): - _logger.debug("Handle %s - invalid key.", handle) - self._contact_offline(handle) - return - if not props.has_key('color'): - _logger.debug("Handle %s - invalid color.", handle) - self._contact_offline(handle) - return - - self._conn[CONN_INTERFACE_ALIASING].RequestAliases([handle], - reply_handler=lambda *args: self._contact_online_aliases_cb( - handle, props, *args), - error_handler=lambda e: self._contact_online_aliases_error_cb( - handle, props, True, e)) - - def _contact_online_request_properties(self, handle, tries): - self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle, - byte_arrays=True, - reply_handler=lambda *args: self._contact_online_properties_cb( - handle, *args), - error_handler=lambda e: self._contact_online_properties_error_cb( - handle, tries, e)) - return False - - def _contact_online_properties_error_cb(self, handle, tries, err): - """Handle error retrieving property-set for a user (handle)""" - if tries <= 3: - _logger.debug("Handle %s - Error getting properties (will retry):" - " %s", handle, err) - tries += 1 - gobject.timeout_add(1000, self._contact_online_request_properties, - handle, tries) - else: - _logger.debug("Handle %s - Error getting properties: %s", - handle, err) - self._contact_offline(handle) - - def _contact_online(self, handle): - """Handle a contact coming online""" - if (handle not in self._subscribe_members and - handle not in self._subscribe_local_pending and - handle not in self._subscribe_remote_pending): - # it's probably a channel-specific handle - can't create a Buddy - # object for those yet - return - - self._online_contacts[handle] = None - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - jid = self._conn[CONN_INTERFACE].InspectHandles( - HANDLE_TYPE_CONTACT, [handle])[0] - self._online_contacts[handle] = jid - # ignore network events for Owner property changes since those - # are handled locally - return - - self._contact_online_request_properties(handle, 1) - - def _subscribe_members_changed_cb(self, message, added, removed, - local_pending, remote_pending, - actor, reason): - - added = set(added) - removed = set(removed) - local_pending = set(local_pending) - remote_pending = set(remote_pending) - - affected = added|removed - affected |= local_pending - affected |= remote_pending - - self._subscribe_members -= affected - self._subscribe_members |= added - self._subscribe_local_pending -= affected - self._subscribe_local_pending |= local_pending - self._subscribe_remote_pending -= affected - self._subscribe_remote_pending |= remote_pending - - def _publish_members_changed_cb(self, added, removed, local_pending, - remote_pending, actor, reason): - - if local_pending: - # accept all requested subscriptions - self._publish_channel[CHANNEL_INTERFACE_GROUP].AddMembers( - local_pending, '') - - # subscribe to people who've subscribed to us, if necessary - added = list(set(added) - self._subscribe_members - - self._subscribe_remote_pending) - if added: - self._subscribe_channel[CHANNEL_INTERFACE_GROUP].AddMembers( - added, '') - - def _presence_update_cb(self, presence): - """Send update for online/offline status of presence""" - for handle in presence: - timestamp, statuses = presence[handle] - online = handle in self._online_contacts - for status, params in statuses.items(): - if not online and status == "offline": - # weren't online in the first place... - continue - jid = self._conn[CONN_INTERFACE].InspectHandles( - HANDLE_TYPE_CONTACT, [handle])[0] - olstr = "ONLINE" - if not online: olstr = "OFFLINE" - _logger.debug("Handle %s (%s) was %s, status now '%s'.", - handle, jid, olstr, status) - if not online and status in ["available", "away", "brb", - "busy", "dnd", "xa"]: - self._contact_online(handle) - elif status in ["offline", "invisible"]: - self._contact_offline(handle) - - def _request_avatar_cb(self, handle, new_avatar_token, avatar, mime_type): - jid = self._online_contacts[handle] - if not jid: - logging.debug("Handle %s not valid yet..." % handle) - return - icon = ''.join(map(chr, avatar)) - self._icon_cache.store_icon(jid, new_avatar_token, icon) - self.emit("avatar-updated", handle, icon) - - def _avatar_updated_cb(self, handle, new_avatar_token): - """Handle update of given user (handle)'s avatar""" - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner property changes since those - # are handled locally - return - - if not self._online_contacts.has_key(handle): - _logger.debug("Handle %s unknown.", handle) - return - - jid = self._online_contacts[handle] - if not jid: - _logger.debug("Handle %s not valid yet...", handle) - return - - icon = self._icon_cache.get_icon(jid, new_avatar_token) - if not icon: - # cache miss - self._conn[CONN_INTERFACE_AVATARS].RequestAvatar(handle, - reply_handler=lambda *args: self._request_avatar_cb(handle, - new_avatar_token, *args), - error_handler=lambda e: self._log_error_cb( - "getting avatar", e)) - else: - self.emit("avatar-updated", handle, icon) - - def _alias_changed_cb(self, aliases): - """Handle update of aliases for all users""" - for handle, alias in aliases: - prop = {'nick': alias} - #print "Buddy %s alias changed to %s" % (handle, alias) - if (self._online_contacts.has_key(handle) and - self._online_contacts[handle]): - self._buddy_properties_changed_cb(handle, prop) - - def _buddy_properties_changed_cb(self, handle, properties): - """Handle update of given user (handle)'s properties""" - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner property changes since those - # are handled locally - return - if (self._online_contacts.has_key(handle) and - self._online_contacts[handle]): - self.emit("buddy-properties-changed", handle, properties) - - def _buddy_activities_changed_cb(self, handle, activities): - """Handle update of given user (handle)'s activities""" - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner activity changes since those - # are handled locally - return - if (not self._online_contacts.has_key(handle) or - not self._online_contacts[handle]): - return - - for act_id, act_handle in activities: - self._activities[act_id] = act_handle - activities_id = map(lambda x: x[0], activities) - self.emit("buddy-activities-changed", handle, activities_id) - - def _buddy_current_activity_changed_cb(self, handle, activity, channel): - """Handle update of given user (handle)'s current activity""" - - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner current activity changes since - # those are handled locally - return - if (not self._online_contacts.has_key(handle) or - not self._online_contacts[handle]): - return - - if not len(activity) or not util.validate_activity_id(activity): - activity = None - prop = {'current-activity': activity} - _logger.debug("Handle %s: current activity now %s", handle, activity) - self._buddy_properties_changed_cb(handle, prop) - - def _new_channel_cb(self, object_path, channel_type, handle_type, handle, - suppress_handler): - """Handle creation of a new channel - """ - if (handle_type == HANDLE_TYPE_ROOM and - channel_type == CHANNEL_TYPE_TEXT): - def ready(channel): - - for act_id, act_handle in self._activities.iteritems(): - if handle == act_handle: - break - else: - return - - def got_all_members(current, local_pending, remote_pending): - if local_pending: - for act_id, act_handle in self._activities.iteritems(): - if handle == act_handle: - self.emit('activity-invitation', act_id) - def got_all_members_err(e): - logger.debug('Unable to get channel members for %s:', - object_path, exc_info=1) - - group = channel[CHANNEL_INTERFACE_GROUP] - group.GetAllMembers(reply_handler=got_all_members, - error_handler=got_all_members_err) - - # we throw away the channel as soon as ready() finishes - Channel(self._conn.service_name, object_path, - ready_handler=ready) - - elif (handle_type == HANDLE_TYPE_CONTACT and - channel_type in (CHANNEL_TYPE_TEXT, - CHANNEL_TYPE_STREAMED_MEDIA)): - self.emit("private-invitation", object_path) - - def update_activity_properties(self, act_id): - """Request update from network on the activity properties of act_id""" - handle = self._activities.get(act_id) - if not handle: - raise RuntimeError("Unknown activity %s: couldn't find handle.") - - self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].GetProperties(handle, - reply_handler=lambda *args: self._activity_properties_changed_cb( - handle, *args), - error_handler=lambda e: self._log_error_cb( - "getting activity properties", e)) - - def set_activity_properties(self, act_id, props): - """Send update to network on the activity properties of act_id (props). - """ - handle = self._activities.get(act_id) - if not handle: - raise RuntimeError("Unknown activity %s: couldn't find handle.") - - self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].SetProperties(handle, - props, reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb( - "setting activity properties", e)) - - def _activity_properties_changed_cb(self, room, properties): - """Handle update of properties for a "room" (activity handle)""" - for act_id, act_handle in self._activities.items(): - if room == act_handle: - self.emit("activity-properties-changed", act_id, properties) - return - - def _server_is_trusted(self, hostname): - """Return True if the server with the given hostname is trusted to - verify public-key ownership correctly, and only allows users to - register JIDs whose username part is either a public key fingerprint, - or of the wrong form to be a public key fingerprint (to allow for - ejabberd's admin@example.com address). - - If we trust the server, we can skip verifying the key ourselves, - which leads to simplifications. In the current implementation we - never verify that people actually own the key they claim to, so - we will always give contacts on untrusted servers a JID- rather than - key-based identity. - - For the moment we assume that the test server, olpc.collabora.co.uk, - does this verification. - """ - return (hostname == 'olpc.collabora.co.uk') - - def identify_contacts(self, tp_chan, handles): - """Work out the "best" unique identifier we can for the given handles, - in the context of the given channel (which may be None), using only - 'fast' connection manager API (that does not involve network - round-trips). - - For the XMPP server case, we proceed as follows: - - * Find the owners of the given handles, if the channel has - channel-specific handles - * If the owner (globally-valid JID) is on a trusted server, return - 'keyid/' plus the 'key fingerprint' (the user part of their JID, - currently implemented as the SHA-1 of the Base64 blob in - owner.key.pub) - * If the owner (globally-valid JID) cannot be found or is on an - untrusted server, return 'xmpp/' plus an escaped form of the JID - - The idea is that we identify buddies by key-ID (i.e. by key, assuming - no collisions) if we can find it without making network round-trips, - but if that's not possible we just use their JIDs. - - :Parameters: - `tp_chan` : telepathy.client.Channel or None - The channel in which the handles were found, or None if they - are known to be channel-specific handles - `handles` : iterable over (int or long) - The contacts' handles in that channel - :Returns: - A dict mapping the provided handles to the best available - unique identifier, which is a string that could be used as a - suffix to an object path - """ - # we need to be able to index into handles, so force them to - # be a sequence - if not isinstance(handles, (tuple, list)): - handles = tuple(handles) - - owners = handles - - if tp_chan is not None and CHANNEL_INTERFACE_GROUP in tp_chan: - group = tp_chan[CHANNEL_INTERFACE_GROUP] - if (group.GetGroupFlags() & - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES): - owners = group.GetHandleOwners(handles) - for i, owner in enumerate(owners): - if owner == 0: - owners[i] = handles[i] - else: - group = None - - jids = self._conn[CONN_INTERFACE].InspectHandles(HANDLE_TYPE_CONTACT, - owners) - - ret = {} - for handle, jid in zip(handles, jids): - # special-case the Owner - we always know who we are - if (handle == self.self_handle or - (group is not None and handle == group.GetSelfHandle())): - ret[handle] = self._owner.props.objid - continue - - if '/' in jid: - # the contact is unidentifiable (in an anonymous MUC) - create - # a temporary identity for them, based on their room-JID - ret[handle] = 'xmpp/' + psutils.escape_identifier(jid) - else: - user, host = jid.split('@', 1) - if (self._server_is_trusted(host) and len(user) == 40 and - user.strip(hexdigits) == ''): - # they're on a trusted server and their username looks - # like a key-ID - ret[handle] = 'keyid/' + user.lower() - else: - # untrusted server, or not the right format to be a - # key-ID - identify the contact by their JID - ret[handle] = 'xmpp/' + psutils.escape_identifier(jid) - - return ret diff --git a/services/presence/sugar-presence-service b/services/presence/sugar-presence-service deleted file mode 100755 index 6ab871c..0000000 --- a/services/presence/sugar-presence-service +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python -# vi: ts=4 ai noet -# -# Copyright (C) 2006, Red Hat, Inc. -# -# 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 logging -import sys -import os - -from sugar import logger -from sugar import env - - -_logger = logging.getLogger('s-p-s') - - -def usage(): - _logger.debug("Usage: sugar-presence-service [<test buddy number (1 - 10)>] [randomize]") - -sys.path.append(env.get_service_path('presence')) - -test_num = 0 -randomize = False -if len(sys.argv) in [2, 3]: - try: - test_num = int(sys.argv[1]) - except ValueError: - _logger.debug("Bad test user number.") - if test_num < 1 or test_num > 10: - _logger.debug("Bad test user number.") - - if len(sys.argv) == 3 and sys.argv[2] == "randomize": - randomize = True -elif len(sys.argv) == 1: - pass -else: - usage() - os._exit(1) - -if test_num > 0: - logger.start('test-%d-presenceservice' % test_num) -else: - logger.start('presenceservice') - -import presenceservice - -_logger.info('Starting presence service...') - -presenceservice.main(test_num, randomize) diff --git a/services/presence/test_psutils.py b/services/presence/test_psutils.py deleted file mode 100644 index 7436d98..0000000 --- a/services/presence/test_psutils.py +++ /dev/null @@ -1,12 +0,0 @@ -print "Running test_psutils..." - -from psutils import escape_identifier, pubkey_to_keyid - -assert pubkey_to_keyid('abc') == 'a9993e364706816aba3e25717850c26c9cd0d89d' - -assert escape_identifier('') == '_' -assert escape_identifier('_') == '_5f' -assert escape_identifier('1') == '_31' -assert escape_identifier('a1') == 'a1' -assert escape_identifier('1a') == '_31a' -assert escape_identifier("0123abc_xyz\x01\xff") == '_30123abc_5fxyz_01_ff' |