Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorSimon 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)
commit129ada9101d733130f8b0e1d56bae140b8f9327c (patch)
tree8a38bd20dc31c5311919e4241b864a46e0b33558 /services
parentdee75374626528ea3398d2ac67bf852c8a98838b (diff)
services/presence/: remove. Use projects/presence-service git repo instead
Diffstat (limited to 'services')
-rw-r--r--services/Makefile.am2
-rw-r--r--services/presence/Makefile.am31
-rw-r--r--services/presence/__init__.py36
-rw-r--r--services/presence/activity.py715
-rw-r--r--services/presence/buddy.py638
-rw-r--r--services/presence/buddyiconcache.py105
-rw-r--r--services/presence/linklocal_plugin.py27
-rw-r--r--services/presence/org.laptop.Sugar.Presence.service.in4
-rw-r--r--services/presence/presenceservice.py517
-rw-r--r--services/presence/pstest.py317
-rw-r--r--services/presence/psutils.py259
-rw-r--r--services/presence/server_plugin.py1171
-rwxr-xr-xservices/presence/sugar-presence-service63
-rw-r--r--services/presence/test_psutils.py12
14 files changed, 1 insertions, 3896 deletions
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'