services/presence/: remove. Use projects/presence-service git repo instead

master
Simon McVittie 17 years ago
parent dee7537462
commit 129ada9101

@ -43,7 +43,6 @@ data/Makefile
lib/Makefile
lib/xdgmime/Makefile
services/Makefile
services/presence/Makefile
services/clipboard/Makefile
shell/Makefile
shell/extensions/Makefile

@ -1 +1 @@
SUBDIRS = presence clipboard console
SUBDIRS = clipboard console

@ -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)

@ -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
"""

@ -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)

@ -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)

@ -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"

@ -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

@ -1,4 +0,0 @@
[D-BUS Service]
Name = org.laptop.Sugar.Presence
Exec = @bindir@/sugar-presence-service

@ -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()

@ -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])

@ -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

File diff suppressed because it is too large Load Diff

@ -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)

@ -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'
Loading…
Cancel
Save