services/presence/: remove. Use projects/presence-service git repo instead
This commit is contained in:
parent
dee7537462
commit
129ada9101
@ -43,7 +43,6 @@ data/Makefile
|
|||||||
lib/Makefile
|
lib/Makefile
|
||||||
lib/xdgmime/Makefile
|
lib/xdgmime/Makefile
|
||||||
services/Makefile
|
services/Makefile
|
||||||
services/presence/Makefile
|
|
||||||
services/clipboard/Makefile
|
services/clipboard/Makefile
|
||||||
shell/Makefile
|
shell/Makefile
|
||||||
shell/extensions/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…
Reference in New Issue
Block a user