Cut over to new PresenceService
This commit is contained in:
@@ -1,176 +0,0 @@
|
||||
# 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 dbus
|
||||
|
||||
PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp"
|
||||
ACTIVITY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Activities/"
|
||||
ACTIVITY_DBUS_INTERFACE = "org.laptop.Presence.Activity"
|
||||
|
||||
|
||||
class ActivityDBusHelper(dbus.service.Object):
|
||||
def __init__(self, parent, bus_name, object_path):
|
||||
self._parent = parent
|
||||
self._bus_name = bus_name
|
||||
self._object_path = object_path
|
||||
dbus.service.Object.__init__(self, bus_name, self._object_path)
|
||||
|
||||
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="ao")
|
||||
def getServicesOfType(self, stype):
|
||||
ret = []
|
||||
for serv in self._parent.get_services_of_type(stype):
|
||||
ret.append(serv.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def getServices(self):
|
||||
ret = []
|
||||
for serv in self._parent.get_services():
|
||||
ret.append(serv.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="s")
|
||||
def getId(self):
|
||||
return self._parent.get_id()
|
||||
|
||||
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="s")
|
||||
def getColor(self):
|
||||
return self._parent.get_color()
|
||||
|
||||
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def getJoinedBuddies(self):
|
||||
ret = []
|
||||
for buddy in self._parent.get_joined_buddies():
|
||||
ret.append(buddy.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ServiceAppeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ServiceDisappeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def BuddyJoined(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def BuddyLeft(self, object_path):
|
||||
pass
|
||||
|
||||
|
||||
class Activity(object):
|
||||
def __init__(self, bus_name, object_id, initial_service):
|
||||
if not initial_service.get_activity_id():
|
||||
raise ValueError("Service must have a valid Activity ID")
|
||||
self._activity_id = initial_service.get_activity_id()
|
||||
|
||||
self._buddies = []
|
||||
self._services = {} # service type -> list of Services
|
||||
self._color = None
|
||||
self._valid = False
|
||||
|
||||
self._object_id = object_id
|
||||
self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id
|
||||
self._dbus_helper = ActivityDBusHelper(self, bus_name, self._object_path)
|
||||
|
||||
self.add_service(initial_service)
|
||||
|
||||
def object_path(self):
|
||||
return dbus.ObjectPath(self._object_path)
|
||||
|
||||
def is_valid(self):
|
||||
"""An activity is only valid when it's color is available."""
|
||||
return self._valid
|
||||
|
||||
def get_id(self):
|
||||
return self._activity_id
|
||||
|
||||
def get_color(self):
|
||||
return self._color
|
||||
|
||||
def get_services(self):
|
||||
ret = []
|
||||
for serv_list in self._services.values():
|
||||
for service in serv_list:
|
||||
if service not in ret:
|
||||
ret.append(service)
|
||||
return ret
|
||||
|
||||
def get_services_of_type(self, stype):
|
||||
if self._services.has_key(stype):
|
||||
return self._services[stype]
|
||||
return []
|
||||
|
||||
def get_joined_buddies(self):
|
||||
buddies = []
|
||||
for serv_list in self._services.values():
|
||||
for serv in serv_list:
|
||||
owner = serv.get_owner()
|
||||
if owner and not owner in buddies and owner.is_valid():
|
||||
buddies.append(owner)
|
||||
return buddies
|
||||
|
||||
def add_service(self, service):
|
||||
stype = service.get_type()
|
||||
if not self._services.has_key(stype):
|
||||
self._services[stype] = []
|
||||
|
||||
if not self._color:
|
||||
color = service.get_one_property('color')
|
||||
if color:
|
||||
self._color = color
|
||||
self._valid = True
|
||||
|
||||
# Send out the BuddyJoined signal if this is the first
|
||||
# service from the buddy that we've seen
|
||||
buddies = self.get_joined_buddies()
|
||||
serv_owner = service.get_owner()
|
||||
if serv_owner and serv_owner not in buddies and serv_owner.is_valid():
|
||||
self._dbus_helper.BuddyJoined(serv_owner.object_path())
|
||||
serv_owner.add_activity(self)
|
||||
|
||||
if not service in self._services[stype]:
|
||||
self._services[stype].append(service)
|
||||
self._dbus_helper.ServiceAppeared(service.object_path())
|
||||
|
||||
def remove_service(self, service):
|
||||
stype = service.get_type()
|
||||
if not self._services.has_key(stype):
|
||||
return
|
||||
self._services[stype].remove(service)
|
||||
self._dbus_helper.ServiceDisappeared(service.object_path())
|
||||
if len(self._services[stype]) == 0:
|
||||
del self._services[stype]
|
||||
|
||||
# Send out the BuddyLeft signal if this is the last
|
||||
# service from the buddy
|
||||
buddies = self.get_joined_buddies()
|
||||
serv_owner = service.get_owner()
|
||||
if serv_owner and serv_owner not in buddies and serv_owner.is_valid():
|
||||
serv_owner.remove_activity(self)
|
||||
self._dbus_helper.BuddyLeft(serv_owner.object_path())
|
||||
@@ -1,533 +0,0 @@
|
||||
# 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 base64
|
||||
import logging
|
||||
|
||||
import gobject
|
||||
import dbus, dbus.service
|
||||
from sugar import profile
|
||||
from sugar.graphics import xocolor
|
||||
|
||||
|
||||
PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp"
|
||||
BUDDY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Buddies/"
|
||||
BUDDY_DBUS_INTERFACE = "org.laptop.Presence.Buddy"
|
||||
|
||||
_BUDDY_KEY_COLOR = 'color'
|
||||
_BUDDY_KEY_CURACT = 'curact'
|
||||
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
|
||||
class BuddyDBusHelper(dbus.service.Object):
|
||||
def __init__(self, parent, bus_name, object_path):
|
||||
self._parent = parent
|
||||
self._bus_name = bus_name
|
||||
self._object_path = object_path
|
||||
dbus.service.Object.__init__(self, bus_name, self._object_path)
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ServiceAppeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ServiceDisappeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="")
|
||||
def Disappeared(self):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="ao")
|
||||
def CurrentActivityChanged(self, activities):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="")
|
||||
def IconChanged(self):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def JoinedActivity(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def LeftActivity(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
|
||||
signature="as")
|
||||
def PropertyChanged(self, prop_list):
|
||||
pass
|
||||
|
||||
@dbus.service.method(BUDDY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ay")
|
||||
def getIcon(self):
|
||||
icon = self._parent.get_icon()
|
||||
if not icon:
|
||||
return ""
|
||||
return icon
|
||||
|
||||
@dbus.service.method(BUDDY_DBUS_INTERFACE,
|
||||
in_signature="so", out_signature="o")
|
||||
def getServiceOfType(self, stype, activity_op):
|
||||
activity = None
|
||||
# "/" is the placeholder for None
|
||||
if activity_op != "/":
|
||||
for act in self._parent.get_joined_activities():
|
||||
if act.object_path() == activity_op:
|
||||
activity = act
|
||||
if not activity:
|
||||
raise NotFoundError("Not found")
|
||||
|
||||
service = self._parent.get_service_of_type(stype, activity)
|
||||
if not service:
|
||||
raise NotFoundError("Not found")
|
||||
return service.object_path()
|
||||
|
||||
@dbus.service.method(BUDDY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def getJoinedActivities(self):
|
||||
acts = []
|
||||
for act in self._parent.get_joined_activities():
|
||||
acts.append(act.object_path())
|
||||
return acts
|
||||
|
||||
@dbus.service.method(BUDDY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="a{sv}")
|
||||
def getProperties(self):
|
||||
props = {}
|
||||
props['name'] = self._parent.get_name()
|
||||
addr = self._parent.get_address()
|
||||
if addr:
|
||||
props['ip4_address'] = addr
|
||||
props['owner'] = self._parent.is_owner()
|
||||
color = self._parent.get_color()
|
||||
if color:
|
||||
props[_BUDDY_KEY_COLOR] = color
|
||||
return props
|
||||
|
||||
@dbus.service.method(BUDDY_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="o")
|
||||
def getCurrentActivity(self):
|
||||
activity = self._parent.get_current_activity()
|
||||
if not activity:
|
||||
raise NotFoundError()
|
||||
return activity.object_path()
|
||||
|
||||
class Buddy(object):
|
||||
"""Represents another person on the network and keeps track of the
|
||||
activities and resources they make available for sharing."""
|
||||
|
||||
def __init__(self, bus_name, object_id, service, icon_cache):
|
||||
if not bus_name:
|
||||
raise ValueError("DBus bus name must be valid")
|
||||
if not object_id or not isinstance(object_id, int):
|
||||
raise ValueError("object id must be a valid number")
|
||||
# Normal Buddy objects must be created with a valid service,
|
||||
# owner objects do not
|
||||
if not isinstance(self, Owner):
|
||||
if not isinstance(service, Service.Service):
|
||||
raise ValueError("service must be a valid service object")
|
||||
|
||||
self._services = {}
|
||||
self._activities = {}
|
||||
|
||||
self._icon_cache = icon_cache
|
||||
|
||||
self._nick_name = None
|
||||
self._address = None
|
||||
if service is not None:
|
||||
self._nick_name = service.get_name()
|
||||
self._address = service.get_source_address()
|
||||
self._color = None
|
||||
self._current_activity = None
|
||||
self._valid = False
|
||||
self._icon = None
|
||||
self._icon_tries = 0
|
||||
|
||||
self._object_id = object_id
|
||||
self._object_path = BUDDY_DBUS_OBJECT_PATH + str(self._object_id)
|
||||
self._dbus_helper = BuddyDBusHelper(self, bus_name, self._object_path)
|
||||
|
||||
self._buddy_presence_service = None
|
||||
if service is not None:
|
||||
self.add_service(service)
|
||||
|
||||
def object_path(self):
|
||||
return dbus.ObjectPath(self._object_path)
|
||||
|
||||
def _request_buddy_icon_cb(self, result_status, response, user_data):
|
||||
"""Callback when icon request has completed."""
|
||||
from sugar.p2p import network
|
||||
icon = response
|
||||
service = user_data
|
||||
if result_status == network.RESULT_SUCCESS:
|
||||
if icon and len(icon):
|
||||
icon = base64.b64decode(icon)
|
||||
self._set_icon(icon)
|
||||
self._icon_cache.add_icon(icon)
|
||||
|
||||
if (result_status == network.RESULT_FAILED or not icon) and self._icon_tries < 3:
|
||||
self._icon_tries = self._icon_tries + 1
|
||||
if self._icon_tries >= 3:
|
||||
logging.debug("Failed to retrieve buddy icon for '%s'." % self._nick_name)
|
||||
gobject.timeout_add(1000, self._get_buddy_icon, service, True)
|
||||
return False
|
||||
|
||||
def _get_buddy_icon(self, service, retry=False):
|
||||
"""Get the buddy's icon. Check the cache first, if its
|
||||
not there get the icon from the buddy over the network."""
|
||||
if retry != True:
|
||||
# Only hit the cache once
|
||||
icon_hash = service.get_one_property('icon-hash')
|
||||
if icon_hash is not None:
|
||||
icon = self._icon_cache.get_icon(icon_hash)
|
||||
if icon:
|
||||
logging.debug("%s: icon cache hit for %s." % (self._nick_name, icon_hash))
|
||||
self._set_icon(icon)
|
||||
return False
|
||||
logging.debug("%s: icon cache miss, fetching icon from buddy..." % self._nick_name)
|
||||
|
||||
from sugar.p2p import Stream
|
||||
buddy_stream = Stream.Stream.new_from_service(service, start_reader=False)
|
||||
writer = buddy_stream.new_writer(service)
|
||||
success = writer.custom_request("get_buddy_icon", self._request_buddy_icon_cb, service)
|
||||
if not success:
|
||||
del writer, buddy_stream
|
||||
gobject.timeout_add(1000, self._get_buddy_icon, service, True)
|
||||
return False
|
||||
|
||||
def _get_service_key(self, service):
|
||||
return (service.get_type(), service.get_activity_id())
|
||||
|
||||
def add_service(self, service):
|
||||
"""Adds a new service to this buddy's service list, returning
|
||||
True if the service was successfully added, and False if it was not."""
|
||||
if service.get_name() != self._nick_name:
|
||||
logging.error("Service and buddy nick names doesn't match: " \
|
||||
"%s %s" % (service.get_name(), self._nick_name))
|
||||
return False
|
||||
|
||||
source_addr = service.get_source_address()
|
||||
if source_addr != self._address:
|
||||
logging.error("Service source and buddy address doesn't " \
|
||||
"match: %s %s" % (source_addr, self._address))
|
||||
return False
|
||||
return self._internal_add_service(service)
|
||||
|
||||
def _internal_add_service(self, service):
|
||||
service_key = self._get_service_key(service)
|
||||
if service_key in self._services.keys():
|
||||
logging.error("Service already known: %s %s" % (service_key[0],
|
||||
service_key[1]))
|
||||
return False
|
||||
|
||||
if service.get_type() == PRESENCE_SERVICE_TYPE and self._buddy_presence_service:
|
||||
# already have a presence service for this buddy
|
||||
logging.debug("!!! Tried to add a buddy presence service when " \
|
||||
"one already existed.")
|
||||
return False
|
||||
|
||||
logging.debug("Buddy %s added service type %s id %s" % (self._nick_name,
|
||||
service.get_type(), service.get_activity_id()))
|
||||
self._services[service_key] = service
|
||||
service.set_owner(self)
|
||||
|
||||
if service.get_type() == PRESENCE_SERVICE_TYPE:
|
||||
self._buddy_presence_service = service
|
||||
# A buddy isn't valid until its official presence
|
||||
# service has been found and resolved
|
||||
self._valid = True
|
||||
### TRIAL1: disable
|
||||
#self._get_buddy_icon(service)
|
||||
color = service.get_one_property(_BUDDY_KEY_COLOR)
|
||||
if xocolor.is_valid(color):
|
||||
self._color = color
|
||||
self._current_activity = service.get_one_property(_BUDDY_KEY_CURACT)
|
||||
# Monitor further buddy property changes, like current activity
|
||||
# and color
|
||||
service.connect('property-changed',
|
||||
self.__buddy_presence_service_property_changed_cb)
|
||||
|
||||
if self._valid:
|
||||
self._dbus_helper.ServiceAppeared(service.object_path())
|
||||
return True
|
||||
|
||||
def __buddy_presence_service_property_changed_cb(self, service, keys):
|
||||
if _BUDDY_KEY_COLOR in keys:
|
||||
new_color = service.get_one_property(_BUDDY_KEY_COLOR)
|
||||
if new_color and self._color != new_color and xocolor.is_valid(new_color):
|
||||
self._color = new_color
|
||||
self._dbus_helper.PropertyChanged([_BUDDY_KEY_COLOR])
|
||||
if _BUDDY_KEY_CURACT in keys:
|
||||
# Three cases here:
|
||||
# 1) Buddy didn't publish a 'curact' key at all; we do nothing
|
||||
# 2) Buddy published a blank/zero-length 'curact' key; we send
|
||||
# a current-activity-changed signal for no activity
|
||||
# 3) Buddy published a non-zero-length 'curact' key; we send
|
||||
# a current-activity-changed signal if we know about the
|
||||
# activity already, if not we postpone until the activity
|
||||
# is found on the network and added to the buddy
|
||||
new_curact = service.get_one_property(_BUDDY_KEY_CURACT)
|
||||
if new_curact and self._current_activity != new_curact:
|
||||
if not len(new_curact):
|
||||
new_curact = None
|
||||
self._current_activity = new_curact
|
||||
if self._activities.has_key(self._current_activity):
|
||||
# Case (3) above, valid activity id
|
||||
activity = self._activities[self._current_activity]
|
||||
if activity.is_valid():
|
||||
self._dbus_helper.CurrentActivityChanged([activity.object_path()])
|
||||
elif not self._current_activity:
|
||||
# Case (2) above, no current activity
|
||||
self._dbus_helper.CurrentActivityChanged([])
|
||||
|
||||
def __find_service_by_activity_id(self, actid):
|
||||
for serv in self._services.values():
|
||||
if serv.get_activity_id() == actid:
|
||||
return serv
|
||||
return None
|
||||
|
||||
def add_activity(self, activity):
|
||||
if activity in self._activities.values():
|
||||
return
|
||||
actid = activity.get_id()
|
||||
if not self.__find_service_by_activity_id(actid):
|
||||
raise RuntimeError("Tried to add activity for which we had no service")
|
||||
self._activities[actid] = activity
|
||||
if activity.is_valid():
|
||||
self._dbus_helper.JoinedActivity(activity.object_path())
|
||||
|
||||
# If when we received a current activity update from the buddy,
|
||||
# but didn't know about that activity yet, and now we do know about
|
||||
# it, we need to send out the changed activity signal
|
||||
if actid == self._current_activity:
|
||||
self._dbus_helper.CurrentActivityChanged([activity.object_path()])
|
||||
|
||||
def remove_service(self, service):
|
||||
"""Remove a service from a buddy; ie, the activity was closed
|
||||
or the buddy went away."""
|
||||
if service.get_source_address() != self._address:
|
||||
return
|
||||
if service.get_name() != self._nick_name:
|
||||
return
|
||||
|
||||
if service.get_type() == PRESENCE_SERVICE_TYPE \
|
||||
and self._buddy_presence_service \
|
||||
and service != self._buddy_presence_service:
|
||||
logging.debug("!!! Tried to remove a spurious buddy presence service.")
|
||||
return
|
||||
|
||||
service_key = self._get_service_key(service)
|
||||
if self._services.has_key(service_key):
|
||||
if self._valid:
|
||||
self._dbus_helper.ServiceDisappeared(service.object_path())
|
||||
del self._services[service_key]
|
||||
|
||||
if service.get_type() == PRESENCE_SERVICE_TYPE:
|
||||
self._valid = False
|
||||
self._dbus_helper.Disappeared()
|
||||
|
||||
def remove_activity(self, activity):
|
||||
actid = activity.get_id()
|
||||
if not self._activities.has_key(actid):
|
||||
return
|
||||
del self._activities[actid]
|
||||
if activity.is_valid():
|
||||
self._dbus_helper.LeftActivity(activity.object_path())
|
||||
|
||||
# If we just removed the buddy's current activity,
|
||||
# send out a signal
|
||||
if actid == self._current_activity:
|
||||
self._current_activity = None
|
||||
self._dbus_helper.CurrentActivityChanged([])
|
||||
|
||||
def get_joined_activities(self):
|
||||
acts = []
|
||||
for act in self._activities.values():
|
||||
if act.is_valid():
|
||||
acts.append(act)
|
||||
return acts
|
||||
|
||||
def get_service_of_type(self, stype, activity=None):
|
||||
"""Return a service of a certain type, or None if the buddy
|
||||
doesn't provide that service."""
|
||||
if not stype:
|
||||
raise RuntimeError("Need to specify a service type.")
|
||||
|
||||
if activity and not activity.is_valid():
|
||||
raise RuntimeError("Activity is not yet valid.")
|
||||
|
||||
if activity:
|
||||
key = (stype, activity.get_id())
|
||||
else:
|
||||
key = (stype, None)
|
||||
if self._services.has_key(key):
|
||||
return self._services[key]
|
||||
return None
|
||||
|
||||
def is_valid(self):
|
||||
"""Return whether the buddy is valid or not. A buddy is
|
||||
not valid until its official presence service has been found
|
||||
and successfully resolved."""
|
||||
return self._valid
|
||||
|
||||
def get_icon(self):
|
||||
"""Return the buddies icon, if any."""
|
||||
return self._icon
|
||||
|
||||
def get_address(self):
|
||||
return self._address
|
||||
|
||||
def get_name(self):
|
||||
return self._nick_name
|
||||
|
||||
def get_color(self):
|
||||
return self._color
|
||||
|
||||
def get_current_activity(self):
|
||||
if not self._current_activity:
|
||||
return None
|
||||
if not self._activities.has_key(self._current_activity):
|
||||
return None
|
||||
return self._activities[self._current_activity]
|
||||
|
||||
def _set_icon(self, icon):
|
||||
"""Can only set icon for other buddies. The Owner
|
||||
takes care of setting it's own icon."""
|
||||
if icon != self._icon:
|
||||
self._icon = icon
|
||||
self._dbus_helper.IconChanged()
|
||||
|
||||
def is_owner(self):
|
||||
return False
|
||||
|
||||
|
||||
class Owner(Buddy):
|
||||
"""Class representing the owner of the machine. This is the client
|
||||
portion of the Owner, paired with the server portion in Owner.py."""
|
||||
def __init__(self, ps, bus_name, object_id, icon_cache):
|
||||
Buddy.__init__(self, bus_name, object_id, None, icon_cache)
|
||||
self._nick_name = profile.get_nick_name()
|
||||
self._color = profile.get_color().to_string()
|
||||
self._ps = ps
|
||||
|
||||
def add_service(self, service):
|
||||
"""Adds a new service to this buddy's service list, returning
|
||||
True if the service was successfully added, and False if it was not."""
|
||||
if service.get_name() != self._nick_name:
|
||||
logging.error("Service and buddy nick names doesn't match: " \
|
||||
"%s %s" % (service.get_name(), self._nick_name))
|
||||
return False
|
||||
|
||||
# The Owner initially doesn't have an address, so the first
|
||||
# service added to the Owner determines the owner's address
|
||||
source_addr = service.get_source_address()
|
||||
if self._address is None and service.is_local():
|
||||
self._address = source_addr
|
||||
self._dbus_helper.PropertyChanged(['ip4_address'])
|
||||
|
||||
# The owner bypasses address checks and only cares if
|
||||
# avahi says the service is a local service
|
||||
if not service.is_local():
|
||||
logging.error("Cannot add remote service to owner object.")
|
||||
return False
|
||||
|
||||
logging.debug("Adding owner service %s.%s at %s:%d." % (service.get_name(),
|
||||
service.get_type(), service.get_source_address(),
|
||||
service.get_port()))
|
||||
return self._internal_add_service(service)
|
||||
|
||||
def is_owner(self):
|
||||
return True
|
||||
|
||||
|
||||
#################################################################
|
||||
# Tests
|
||||
#################################################################
|
||||
|
||||
import unittest
|
||||
import Service
|
||||
|
||||
__objid_seq = 0
|
||||
def _next_objid():
|
||||
global __objid_seq
|
||||
__objid_seq = __objid_seq + 1
|
||||
return __objid_seq
|
||||
|
||||
|
||||
class BuddyTestCase(unittest.TestCase):
|
||||
_DEF_NAME = u"Tommy"
|
||||
_DEF_STYPE = unicode(PRESENCE_SERVICE_TYPE)
|
||||
_DEF_DOMAIN = u"local"
|
||||
_DEF_ADDRESS = u"1.1.1.1"
|
||||
_DEF_PORT = 1234
|
||||
|
||||
def __init__(self, name):
|
||||
self._bus = dbus.SessionBus()
|
||||
self._bus_name = dbus.service.BusName('org.laptop.Presence', bus=self._bus)
|
||||
unittest.TestCase.__init__(self, name)
|
||||
|
||||
def __del__(self):
|
||||
del self._bus_name
|
||||
del self._bus
|
||||
|
||||
def _test_init_fail(self, service, fail_msg):
|
||||
"""Test something we expect to fail."""
|
||||
try:
|
||||
objid = _next_objid()
|
||||
buddy = Buddy(self._bus_name, objid, service, owner=False)
|
||||
except ValueError, exc:
|
||||
pass
|
||||
else:
|
||||
self.fail("expected a ValueError for %s." % fail_msg)
|
||||
|
||||
def testService(self):
|
||||
service = None
|
||||
self._test_init_fail(service, "invalid service")
|
||||
|
||||
def testGoodInit(self):
|
||||
objid = _next_objid()
|
||||
service = Service.Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
|
||||
self._DEF_ADDRESS, self._DEF_PORT)
|
||||
objid = _next_objid()
|
||||
buddy = Buddy(self._bus_name, objid, service)
|
||||
assert buddy.get_name() == self._DEF_NAME, "buddy name wasn't correct after init."
|
||||
assert buddy.get_address() == self._DEF_ADDRESS, "buddy address wasn't correct after init."
|
||||
assert buddy.object_path() == BUDDY_DBUS_OBJECT_PATH + str(objid)
|
||||
|
||||
def addToSuite(suite):
|
||||
suite.addTest(BuddyTestCase("testService"))
|
||||
suite.addTest(BuddyTestCase("testGoodInit"))
|
||||
addToSuite = staticmethod(addToSuite)
|
||||
|
||||
|
||||
def main():
|
||||
suite = unittest.TestSuite()
|
||||
BuddyTestCase.addToSuite(suite)
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(suite)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,77 +0,0 @@
|
||||
# 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 os, time, md5
|
||||
from sugar import env
|
||||
from sugar import util
|
||||
|
||||
class BuddyIconCache(object):
|
||||
"""Caches icons on disk and finds them based on md5 hash."""
|
||||
def __init__(self):
|
||||
ppath = env.get_profile_path()
|
||||
self._cachepath = os.path.join(ppath, "cache", "buddy-icons")
|
||||
if not os.path.exists(self._cachepath):
|
||||
os.makedirs(self._cachepath)
|
||||
|
||||
self._cache = {}
|
||||
|
||||
# Read all cached icons and their sums
|
||||
for fname in os.listdir(self._cachepath):
|
||||
m = md5.new()
|
||||
data = self._get_icon_data(fname)
|
||||
if len(data) == 0:
|
||||
continue
|
||||
m.update(data)
|
||||
printable_hash = util.printable_hash(m.digest())
|
||||
self._cache[printable_hash] = fname
|
||||
del m
|
||||
|
||||
def _get_icon_data(self, fname):
|
||||
fd = open(os.path.join(self._cachepath, fname), "r")
|
||||
data = fd.read()
|
||||
fd.close()
|
||||
del fd
|
||||
return data
|
||||
|
||||
def get_icon(self, printable_hash):
|
||||
if not isinstance(printable_hash, unicode):
|
||||
raise RuntimeError("printable_hash must be a unicode string.")
|
||||
try:
|
||||
fname = self._cache[printable_hash]
|
||||
return self._get_icon_data(fname)
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def add_icon(self, icon_data):
|
||||
if len(icon_data) == 0:
|
||||
return
|
||||
|
||||
m = md5.new()
|
||||
m.update(icon_data)
|
||||
printable_hash = util.printable_hash(m.digest())
|
||||
if self._cache.has_key(printable_hash):
|
||||
del m
|
||||
return
|
||||
|
||||
# Write the icon to disk and add an entry to our cache for it
|
||||
m.update(time.asctime())
|
||||
fname = util.printable_hash(m.digest())
|
||||
fd = open(os.path.join(self._cachepath, fname), "w")
|
||||
fd.write(icon_data)
|
||||
fd.close()
|
||||
self._cache[printable_hash] = fname
|
||||
del m
|
||||
@@ -1,21 +1,13 @@
|
||||
servicedir = $(datadir)/dbus-1/services
|
||||
service_in_files = org.laptop.Presence.service.in
|
||||
service_DATA = $(service_in_files:.service.in=.service)
|
||||
sugardir = $(pkgdatadir)/services/presence2
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
activity.py \
|
||||
buddy.py \
|
||||
buddyiconcache.py \
|
||||
linklocal_plugin.py \
|
||||
presenceservice.py \
|
||||
server_plugin.py
|
||||
|
||||
$(service_DATA): $(service_in_files) Makefile
|
||||
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
|
||||
bin_SCRIPTS = sugar-presence-service2
|
||||
|
||||
sugardir = $(pkgdatadir)/services/presence
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
Activity.py \
|
||||
Buddy.py \
|
||||
BuddyIconCache.py \
|
||||
PresenceService.py \
|
||||
Service.py
|
||||
|
||||
bin_SCRIPTS = sugar-presence-service
|
||||
|
||||
DISTCLEANFILES = $(service_DATA)
|
||||
|
||||
EXTRA_DIST = $(service_in_files) $(bin_SCRIPTS)
|
||||
EXTRA_DIST = $(bin_SCRIPTS)
|
||||
|
||||
@@ -1,876 +0,0 @@
|
||||
# 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 avahi, dbus, dbus.glib, gobject
|
||||
import Buddy
|
||||
import Service
|
||||
import Activity
|
||||
import random
|
||||
import logging
|
||||
from sugar import util
|
||||
from sugar import env
|
||||
from sugar import profile
|
||||
import BuddyIconCache
|
||||
|
||||
|
||||
_SA_UNRESOLVED = 0
|
||||
_SA_RESOLVE_PENDING = 1
|
||||
_SA_RESOLVED = 2
|
||||
class ServiceAdv(object):
|
||||
"""Wrapper class to track services from Avahi."""
|
||||
def __init__(self, interface, protocol, name, stype, domain, local):
|
||||
self._interface = interface
|
||||
self._protocol = protocol
|
||||
if not isinstance(name, unicode):
|
||||
raise ValueError("service advertisement name must be unicode.")
|
||||
self._name = name
|
||||
if not isinstance(stype, unicode):
|
||||
raise ValueError("service advertisement type must be unicode.")
|
||||
self._stype = stype
|
||||
if not isinstance(domain, unicode):
|
||||
raise ValueError("service advertisement domain must be unicode.")
|
||||
self._domain = domain
|
||||
self._service = None
|
||||
if not isinstance(local, bool):
|
||||
raise ValueError("local must be a bool.")
|
||||
self._local = local
|
||||
self._state = _SA_UNRESOLVED
|
||||
self._resolver = None
|
||||
self._resolv_tries = 0
|
||||
|
||||
def __del__(self):
|
||||
if self._resolver:
|
||||
del self._resolver
|
||||
|
||||
def interface(self):
|
||||
return self._interface
|
||||
def protocol(self):
|
||||
return self._protocol
|
||||
def name(self):
|
||||
return self._name
|
||||
def stype(self):
|
||||
return self._stype
|
||||
def domain(self):
|
||||
return self._domain
|
||||
def is_local(self):
|
||||
return self._local
|
||||
def resolv_tries(self):
|
||||
return self._resolv_tries
|
||||
def inc_resolv_tries(self):
|
||||
self._resolv_tries += 1
|
||||
def service(self):
|
||||
return self._service
|
||||
def set_service(self, service):
|
||||
if not isinstance(service, Service.Service):
|
||||
raise ValueError("must be a valid service.")
|
||||
if service != self._service:
|
||||
self._service = service
|
||||
def resolver(self):
|
||||
return self._resolver
|
||||
def set_resolver(self, resolver):
|
||||
if resolver and not isinstance(resolver, dbus.Interface):
|
||||
raise ValueError("'resolver' must be a valid dbus object")
|
||||
if not resolver and self._resolver:
|
||||
del self._resolver
|
||||
self._resolver = resolver
|
||||
def state(self):
|
||||
return self._state
|
||||
def set_state(self, state):
|
||||
if state == _SA_RESOLVE_PENDING:
|
||||
if self._state == _SA_RESOLVED:
|
||||
raise ValueError("Can't reset to resolve pending from resolved.")
|
||||
if state == _SA_UNRESOLVED:
|
||||
self._resolv_tries = 0
|
||||
self._state = state
|
||||
|
||||
class RegisteredServiceType(object):
|
||||
def __init__(self, stype):
|
||||
self._stype = stype
|
||||
self._refcount = 1
|
||||
|
||||
def get_type(self):
|
||||
return self._stype
|
||||
|
||||
def ref(self):
|
||||
self._refcount += 1
|
||||
|
||||
def unref(self):
|
||||
self._refcount -= 1
|
||||
return self._refcount
|
||||
|
||||
def _txt_to_dict(txt):
|
||||
"""Convert an avahi-returned TXT record formatted
|
||||
as nested arrays of integers (from dbus) into a dict
|
||||
of key/value string pairs."""
|
||||
prop_dict = {}
|
||||
props = avahi.txt_array_to_string_array(txt)
|
||||
for item in props:
|
||||
key = value = None
|
||||
if '=' not in item:
|
||||
# No = means a boolean value of true
|
||||
key = item
|
||||
value = True
|
||||
else:
|
||||
(key, value) = item.split('=', 1)
|
||||
prop_dict[key] = value
|
||||
return prop_dict
|
||||
|
||||
|
||||
_PRESENCE_SERVICE = "org.laptop.Presence"
|
||||
_PRESENCE_DBUS_INTERFACE = "org.laptop.Presence"
|
||||
_PRESENCE_OBJECT_PATH = "/org/laptop/Presence"
|
||||
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
|
||||
class PresenceServiceDBusHelper(dbus.service.Object):
|
||||
def __init__(self, parent, bus_name):
|
||||
self._parent = parent
|
||||
self._bus_name = bus_name
|
||||
dbus.service.Object.__init__(self, bus_name, _PRESENCE_OBJECT_PATH)
|
||||
|
||||
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def BuddyAppeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def BuddyDisappeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ServiceAppeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ServiceDisappeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ActivityAppeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
|
||||
signature="o")
|
||||
def ActivityDisappeared(self, object_path):
|
||||
pass
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def getServices(self):
|
||||
ret = []
|
||||
for serv in self._parent.get_services():
|
||||
ret.append(serv.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="ao")
|
||||
def getServicesOfType(self, stype):
|
||||
ret = []
|
||||
for serv in self._parent.get_services_of_type(stype):
|
||||
ret.append(serv.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def getActivities(self):
|
||||
ret = []
|
||||
for act in self._parent.get_activities():
|
||||
ret.append(act.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="o")
|
||||
def getActivity(self, actid):
|
||||
act = self._parent.get_activity(actid)
|
||||
if not act:
|
||||
raise NotFoundError("Not found")
|
||||
return act.object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def getBuddies(self):
|
||||
ret = []
|
||||
for buddy in self._parent.get_buddies():
|
||||
ret.append(buddy.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="o")
|
||||
def getBuddyByName(self, name):
|
||||
buddy = self._parent.get_buddy_by_name(name)
|
||||
if not buddy:
|
||||
raise NotFoundError("Not found")
|
||||
return buddy.object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="o")
|
||||
def getBuddyByAddress(self, addr):
|
||||
buddy = self._parent.get_buddy_by_address(addr)
|
||||
if not buddy:
|
||||
raise NotFoundError("Not found")
|
||||
return buddy.object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="o")
|
||||
def getOwner(self):
|
||||
owner = self._parent.get_owner()
|
||||
if not owner:
|
||||
raise NotFoundError("Not found")
|
||||
return owner.object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="os", out_signature="o",
|
||||
sender_keyword="sender")
|
||||
def joinActivity(self, activity_op, stype, sender):
|
||||
found_activity = None
|
||||
acts = self._parent.get_activities()
|
||||
for act in acts:
|
||||
if act.object_path() == activity_op:
|
||||
found_activity = act
|
||||
break
|
||||
if not found_activity:
|
||||
raise NotFoundError("The activity %s was not found." % activity_op)
|
||||
return self._parent.join_activity(found_activity, stype, sender)
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="ssa{ss}sis", out_signature="o",
|
||||
sender_keyword="sender")
|
||||
def shareActivity(self, activity_id, stype, properties, address, port,
|
||||
domain, sender=None):
|
||||
if not len(address):
|
||||
address = None
|
||||
service = self._parent.share_activity(activity_id, stype, properties, address,
|
||||
port, domain, sender)
|
||||
return service.object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="ssa{ss}sis", out_signature="o",
|
||||
sender_keyword="sender")
|
||||
def registerService(self, name, stype, properties, address, port, domain,
|
||||
sender=None):
|
||||
if not len(address):
|
||||
address = None
|
||||
service = self._parent.register_service(name, stype, properties, address,
|
||||
port, domain, sender)
|
||||
return service.object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="o", out_signature="",
|
||||
sender_keyword="sender")
|
||||
def unregisterService(self, service_op, sender):
|
||||
found_serv = None
|
||||
services = self._parent.get_services()
|
||||
for serv in services:
|
||||
if serv.object_path() == service_op:
|
||||
found_serv = serv
|
||||
break
|
||||
if not found_serv:
|
||||
raise NotFoundError("The service %s was not found." % service_op)
|
||||
return self._parent.unregister_service(found_serv, sender)
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="")
|
||||
def registerServiceType(self, stype):
|
||||
self._parent.register_service_type(stype)
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
|
||||
in_signature="s", out_signature="")
|
||||
def unregisterServiceType(self, stype):
|
||||
self._parent.unregister_service_type(stype)
|
||||
|
||||
@dbus.service.method(_PRESENCE_DBUS_INTERFACE)
|
||||
def start(self):
|
||||
self._parent.start()
|
||||
|
||||
class PresenceService(object):
|
||||
def __init__(self):
|
||||
# interface -> IP address: interfaces we've gotten events on so far
|
||||
self._started = False
|
||||
self._local_addrs = {}
|
||||
|
||||
self._next_object_id = 0
|
||||
|
||||
self._buddies = {} # nick -> Buddy
|
||||
self._services = {} # (name, type) -> Service
|
||||
self._activities = {} # activity id -> Activity
|
||||
|
||||
self._service_blacklist = {}
|
||||
|
||||
# Keep track of stuff we're already browsing
|
||||
self._service_type_browsers = {}
|
||||
self._service_browsers = {}
|
||||
|
||||
# Resolved service list
|
||||
self._service_advs = []
|
||||
|
||||
# Service types we care about resolving
|
||||
self._registered_service_types = []
|
||||
|
||||
# Set up the dbus service we provide
|
||||
self._session_bus = dbus.SessionBus()
|
||||
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=self._session_bus)
|
||||
self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name)
|
||||
|
||||
self._icon_cache = BuddyIconCache.BuddyIconCache()
|
||||
|
||||
# Our owner object
|
||||
if profile.get_nick_name():
|
||||
objid = self._get_next_object_id()
|
||||
self._owner = Buddy.Owner(self, self._bus_name,
|
||||
objid, self._icon_cache)
|
||||
self._buddies[self._owner.get_name()] = self._owner
|
||||
else:
|
||||
self._owner = None
|
||||
|
||||
def start(self):
|
||||
if self._started:
|
||||
return
|
||||
self._started = True
|
||||
|
||||
# Connect to Avahi for mDNS stuff
|
||||
self._system_bus = dbus.SystemBus()
|
||||
self._mdns_service = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME,
|
||||
avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
|
||||
|
||||
# Always browse .local
|
||||
self._new_domain_cb(avahi.IF_UNSPEC, avahi.PROTO_INET, "local")
|
||||
|
||||
# Connect to Avahi and start looking for stuff
|
||||
domain_browser = self._mdns_service.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_INET,
|
||||
"", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))
|
||||
db = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, domain_browser), avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
|
||||
db.connect_to_signal('ItemNew', self._new_domain_cb_glue)
|
||||
|
||||
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 get_services(self):
|
||||
return self._services.values()
|
||||
|
||||
def get_services_of_type(self, stype):
|
||||
ret = []
|
||||
for serv in self._services.values():
|
||||
if serv.get_type() == stype:
|
||||
ret.append(serv)
|
||||
return ret
|
||||
|
||||
def get_activities(self):
|
||||
# Only return valid activities
|
||||
ret = []
|
||||
for act in self._activities.values():
|
||||
if act.is_valid():
|
||||
ret.append(act)
|
||||
return ret
|
||||
|
||||
def get_activity(self, actid):
|
||||
if self._activities.has_key(actid):
|
||||
act = self._activities[actid]
|
||||
if act.is_valid():
|
||||
return act
|
||||
return None
|
||||
|
||||
def get_buddies(self):
|
||||
buddies = []
|
||||
for buddy in self._buddies.values():
|
||||
if buddy.is_valid():
|
||||
buddies.append(buddy)
|
||||
return buddies
|
||||
|
||||
def get_buddy_by_name(self, name):
|
||||
if self._buddies.has_key(name):
|
||||
if self._buddies[name].is_valid():
|
||||
return self._buddies[name]
|
||||
return None
|
||||
|
||||
def get_buddy_by_address(self, address):
|
||||
for buddy in self._buddies.values():
|
||||
if buddy.get_address() == address and buddy.is_valid():
|
||||
return buddy
|
||||
return None
|
||||
|
||||
def get_owner(self):
|
||||
return self._owner
|
||||
|
||||
def _find_service_adv(self, interface=None, protocol=None, name=None,
|
||||
stype=None, domain=None, local=None):
|
||||
"""Search a list of service advertisements for ones matching
|
||||
certain criteria."""
|
||||
adv_list = []
|
||||
for adv in self._service_advs:
|
||||
if interface and adv.interface() != interface:
|
||||
continue
|
||||
if protocol and adv.protocol() != protocol:
|
||||
continue
|
||||
if name and adv.name() != name:
|
||||
continue
|
||||
if stype and adv.stype() != stype:
|
||||
continue
|
||||
if domain and adv.domain() != domain:
|
||||
continue
|
||||
if local is not None and adv.is_local() != local:
|
||||
continue
|
||||
adv_list.append(adv)
|
||||
return adv_list
|
||||
|
||||
def _find_registered_service_type(self, stype):
|
||||
for item in self._registered_service_types:
|
||||
if item.get_type() == stype:
|
||||
return item
|
||||
return None
|
||||
|
||||
def _handle_new_service_for_buddy(self, service, local):
|
||||
"""Deal with a new discovered service object."""
|
||||
# Once a service is resolved, we match it up to an existing buddy,
|
||||
# or create a new Buddy if this is the first service known about the buddy
|
||||
buddy_was_valid = False
|
||||
name = service.get_name()
|
||||
buddy = None
|
||||
try:
|
||||
buddy = self._buddies[name]
|
||||
buddy_was_valid = buddy.is_valid()
|
||||
service_added = buddy.add_service(service)
|
||||
if service_added:
|
||||
self._dbus_helper.ServiceAppeared(service.object_path())
|
||||
except KeyError:
|
||||
source_addr = service.get_source_address()
|
||||
objid = self._get_next_object_id()
|
||||
buddy = Buddy.Buddy(self._bus_name, objid, service, self._icon_cache)
|
||||
self._buddies[name] = buddy
|
||||
self._dbus_helper.ServiceAppeared(service.object_path())
|
||||
if not buddy_was_valid and buddy.is_valid():
|
||||
self._dbus_helper.BuddyAppeared(buddy.object_path())
|
||||
return buddy
|
||||
|
||||
def _handle_new_activity_service(self, service):
|
||||
# If the serivce is an activity service, merge it into our activities list
|
||||
actid = service.get_activity_id()
|
||||
if not actid:
|
||||
return
|
||||
activity = None
|
||||
was_valid = False
|
||||
if not self._activities.has_key(actid):
|
||||
objid = self._get_next_object_id()
|
||||
activity = Activity.Activity(self._bus_name, objid, service)
|
||||
self._activities[actid] = activity
|
||||
else:
|
||||
activity = self._activities[actid]
|
||||
was_valid = activity.is_valid()
|
||||
|
||||
if activity:
|
||||
activity.add_service(service)
|
||||
|
||||
# Add the activity to its buddy
|
||||
# FIXME: use something other than name to attribute to buddy
|
||||
try:
|
||||
buddy = self._buddies[service.get_name()]
|
||||
buddy.add_activity(activity)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not was_valid and activity.is_valid():
|
||||
self._dbus_helper.ActivityAppeared(activity.object_path())
|
||||
|
||||
def _handle_remove_activity_service(self, service):
|
||||
actid = service.get_activity_id()
|
||||
if not actid:
|
||||
return
|
||||
if not self._activities.has_key(actid):
|
||||
return
|
||||
|
||||
activity = self._activities[actid]
|
||||
|
||||
activity.remove_service(service)
|
||||
if len(activity.get_services()) == 0:
|
||||
# Remove the activity from its buddy
|
||||
# FIXME: use something other than name to attribute to buddy
|
||||
try:
|
||||
buddy = self._buddies[service.get_name()]
|
||||
buddy.remove_activity(activity)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Kill the activity
|
||||
self._dbus_helper.ActivityDisappeared(activity.object_path())
|
||||
del self._activities[actid]
|
||||
|
||||
def _service_resolved_cb(self, adv, interface, protocol, full_name,
|
||||
stype, domain, host, aprotocol, address, port, txt, flags,
|
||||
updated):
|
||||
"""When the service discovery finally gets here, we've got enough
|
||||
information about the service to assign it to a buddy."""
|
||||
if updated == False:
|
||||
logging.debug("Resolved service '%s' type '%s' domain '%s' to " \
|
||||
" %s:%s" % (full_name, stype, domain, address, port))
|
||||
|
||||
if not adv in self._service_advs:
|
||||
return False
|
||||
if adv.state() != _SA_RESOLVED:
|
||||
return False
|
||||
|
||||
# See if we know about this service already
|
||||
service = None
|
||||
key = (full_name, stype)
|
||||
props = _txt_to_dict(txt)
|
||||
if not self._services.has_key(key):
|
||||
objid = self._get_next_object_id()
|
||||
service = Service.Service(self._bus_name, objid, name=full_name,
|
||||
stype=stype, domain=domain, address=address, port=port,
|
||||
properties=props, source_address=address)
|
||||
self._services[key] = service
|
||||
else:
|
||||
# Already tracking this service; either:
|
||||
# a) we were the one that shared it in the first place,
|
||||
# and therefore the source address would not have
|
||||
# been set yet
|
||||
# b) the service has been updated
|
||||
service = self._services[key]
|
||||
if not service.get_source_address():
|
||||
service.set_source_address(address)
|
||||
if not service.get_address():
|
||||
service.set_address(address)
|
||||
|
||||
adv.set_service(service)
|
||||
|
||||
if service and updated:
|
||||
service.set_properties(props, from_network=True)
|
||||
return False
|
||||
|
||||
# Merge the service into our buddy and activity lists, if needed
|
||||
buddy = self._handle_new_service_for_buddy(service, adv.is_local())
|
||||
if buddy and service.get_activity_id():
|
||||
self._handle_new_activity_service(service)
|
||||
|
||||
return False
|
||||
|
||||
def _service_resolved_cb_glue(self, adv, interface, protocol, name,
|
||||
stype, domain, host, aprotocol, address, port, txt, flags):
|
||||
# Avahi doesn't flag updates to existing services, so we have
|
||||
# to determine that here
|
||||
updated = False
|
||||
if adv.state() == _SA_RESOLVED:
|
||||
updated = True
|
||||
|
||||
adv.set_state(_SA_RESOLVED)
|
||||
gobject.idle_add(self._service_resolved_cb, adv, interface,
|
||||
protocol, name, stype, domain, host, aprotocol, address,
|
||||
port, txt, flags, updated)
|
||||
|
||||
def _service_resolved_failure_cb(self, adv, err):
|
||||
retried = False
|
||||
adv.set_resolver(None)
|
||||
if adv.stype() == Buddy.PRESENCE_SERVICE_TYPE:
|
||||
# Retry the presence service type a few times
|
||||
if adv.resolv_tries() < 4:
|
||||
adv.set_state(_SA_RESOLVE_PENDING)
|
||||
gobject.timeout_add(250, self._resolve_service, adv)
|
||||
retried = True
|
||||
logging.error("Retrying resolution of service %s.%s: %s" % (adv.name(),
|
||||
adv.stype(), err))
|
||||
else:
|
||||
key = adv.name() + "." + adv.stype()
|
||||
if not self._service_blacklist.has_key(key):
|
||||
logging.error("Adding service %s to blacklist" % key)
|
||||
self._service_blacklist[key] = 1
|
||||
|
||||
if not retried:
|
||||
logging.error("Error resolving service %s.%s: %s" % (adv.name(),
|
||||
adv.stype(), err))
|
||||
adv.set_state(_SA_UNRESOLVED)
|
||||
|
||||
def _resolve_service(self, adv):
|
||||
"""Resolve and lookup a ZeroConf service to obtain its address and TXT records."""
|
||||
key = adv.name() + "." + adv.stype()
|
||||
if self._service_blacklist.has_key(key):
|
||||
return False
|
||||
|
||||
# Ask avahi to resolve this particular service
|
||||
try:
|
||||
path = self._mdns_service.ServiceResolverNew(dbus.Int32(adv.interface()),
|
||||
dbus.Int32(adv.protocol()), adv.name(), adv.stype(), adv.domain(),
|
||||
avahi.PROTO_INET, dbus.UInt32(0))
|
||||
except dbus.DBusException, e:
|
||||
if str(e).find("TooManyObjectsError") >= 0:
|
||||
return False
|
||||
raise e
|
||||
|
||||
resolver = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, path),
|
||||
avahi.DBUS_INTERFACE_SERVICE_RESOLVER)
|
||||
resolver.connect_to_signal('Found', lambda *args: self._service_resolved_cb_glue(adv, *args))
|
||||
resolver.connect_to_signal('Failure', lambda *args: self._service_resolved_failure_cb(adv, *args))
|
||||
adv.inc_resolv_tries()
|
||||
adv.set_resolver(resolver)
|
||||
return False
|
||||
|
||||
def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags):
|
||||
local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
|
||||
adv_list = self._find_service_adv(interface=interface, protocol=protocol,
|
||||
name=full_name, stype=stype, domain=domain, local=local)
|
||||
adv = None
|
||||
if not adv_list:
|
||||
adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name,
|
||||
stype=stype, domain=domain, local=local)
|
||||
self._service_advs.append(adv)
|
||||
else:
|
||||
adv = adv_list[0]
|
||||
|
||||
# Decompose service name if we can
|
||||
(actid, buddy_name) = Service.decompose_service_name(full_name)
|
||||
|
||||
# If we care about the service right now, resolve it
|
||||
resolve = False
|
||||
item = self._find_registered_service_type(stype)
|
||||
if actid is not None or item is not None:
|
||||
resolve = True
|
||||
if resolve and adv.state() == _SA_UNRESOLVED:
|
||||
logging.debug("Found '%s' (%d) of type '%s' in domain" \
|
||||
" '%s' on %i.%i; will resolve." % (full_name, flags, stype,
|
||||
domain, interface, protocol))
|
||||
adv.set_state(_SA_RESOLVE_PENDING)
|
||||
gobject.idle_add(self._resolve_service, adv)
|
||||
|
||||
return False
|
||||
|
||||
def _service_appeared_cb_glue(self, interface, protocol, name, stype, domain, flags):
|
||||
gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags)
|
||||
|
||||
def _service_disappeared_cb(self, interface, protocol, full_name, stype, domain, flags):
|
||||
local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
|
||||
# If it's an unresolved service, remove it from our unresolved list
|
||||
adv_list = self._find_service_adv(interface=interface, protocol=protocol,
|
||||
name=full_name, stype=stype, domain=domain, local=local)
|
||||
if not adv_list:
|
||||
return False
|
||||
|
||||
# Get the service object; if none, we have nothing left to do
|
||||
adv = adv_list[0]
|
||||
service = adv.service()
|
||||
self._service_advs.remove(adv)
|
||||
del adv
|
||||
if not service:
|
||||
return False
|
||||
|
||||
logging.debug("Service %s.%s in domain %s on %i.%i disappeared." % (full_name,
|
||||
stype, domain, interface, protocol))
|
||||
|
||||
self._dbus_helper.ServiceDisappeared(service.object_path())
|
||||
self._handle_remove_activity_service(service)
|
||||
|
||||
# Decompose service name if we can
|
||||
(actid, buddy_name) = Service.decompose_service_name(full_name)
|
||||
|
||||
# Remove the service from the buddy
|
||||
try:
|
||||
buddy = self._buddies[buddy_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
buddy.remove_service(service)
|
||||
if not buddy.is_valid():
|
||||
self._dbus_helper.BuddyDisappeared(buddy.object_path())
|
||||
del self._buddies[buddy_name]
|
||||
key = (service.get_full_name(), service.get_type())
|
||||
del self._services[key]
|
||||
return False
|
||||
|
||||
def _service_disappeared_cb_glue(self, interface, protocol, name, stype, domain, flags):
|
||||
gobject.idle_add(self._service_disappeared_cb, interface, protocol, name, stype, domain, flags)
|
||||
|
||||
def _new_service_type_cb(self, interface, protocol, stype, domain, flags):
|
||||
# Are we already browsing this domain for this type?
|
||||
if self._service_browsers.has_key((interface, protocol, stype, domain)):
|
||||
return
|
||||
|
||||
# Start browsing for all services of this type in this domain
|
||||
try:
|
||||
s_browser = self._mdns_service.ServiceBrowserNew(interface,
|
||||
protocol, stype, domain, dbus.UInt32(0))
|
||||
browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, s_browser),
|
||||
avahi.DBUS_INTERFACE_SERVICE_BROWSER)
|
||||
browser_obj.connect_to_signal('ItemNew', self._service_appeared_cb_glue)
|
||||
browser_obj.connect_to_signal('ItemRemove', self._service_disappeared_cb_glue)
|
||||
|
||||
self._service_browsers[(interface, protocol, stype, domain)] = browser_obj
|
||||
except dbus.DBusException:
|
||||
logging.debug("Error browsing service type '%s'" % stype)
|
||||
return False
|
||||
|
||||
def _new_service_type_cb_glue(self, interface, protocol, stype, domain, flags):
|
||||
if len(stype) > 0:
|
||||
gobject.idle_add(self._new_service_type_cb, interface, protocol,
|
||||
stype, domain, flags)
|
||||
|
||||
def _new_domain_cb(self, interface, protocol, domain, flags=0):
|
||||
"""Callback from Avahi when a new domain has been found. Start
|
||||
browsing the new domain."""
|
||||
# Only use .local for now...
|
||||
if domain != "local":
|
||||
return
|
||||
|
||||
# Are we already browsing this domain?
|
||||
if self._service_type_browsers.has_key((interface, protocol, domain)):
|
||||
return
|
||||
|
||||
# Start browsing this domain for the services its members offer
|
||||
try:
|
||||
st_browser = self._mdns_service.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0))
|
||||
browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, st_browser),
|
||||
avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER)
|
||||
except dbus.DBusException, exc:
|
||||
str_exc = str(exc)
|
||||
logging.error("got exception %s while attempting to browse domain %s on %i.%i" % (str_exc, domain, interface, protocol))
|
||||
if str_exc.find("The name org.freedesktop.Avahi was not provided by any .service files") >= 0:
|
||||
raise Exception("Avahi does not appear to be running. '%s'" % str_exc)
|
||||
else:
|
||||
raise exc
|
||||
logging.debug("Browsing domain '%s' on %i.%i ..." % (domain, interface, protocol))
|
||||
browser_obj.connect_to_signal('ItemNew', self._new_service_type_cb_glue)
|
||||
self._service_type_browsers[(interface, protocol, domain)] = browser_obj
|
||||
return False
|
||||
|
||||
def _new_domain_cb_glue(self, interface, protocol, domain, flags=0):
|
||||
gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags)
|
||||
|
||||
def join_activity(self, activity, stype, sender):
|
||||
services = activity.get_services_of_type(stype)
|
||||
if not len(services):
|
||||
raise NotFoundError("The service type %s was not present within " \
|
||||
"the activity %s" % (stype, activity.object_path()))
|
||||
act_service = services[0]
|
||||
props = act_service.get_properties()
|
||||
color = activity.get_color()
|
||||
if color:
|
||||
props['color'] = color
|
||||
return self._share_activity(activity.get_id(), stype, properties,
|
||||
act_service.get_address(), act_service.get_port(),
|
||||
act_service.get_domain(), sender)
|
||||
|
||||
def share_activity(self, activity_id, stype, properties=None, address=None,
|
||||
port=-1, domain=u"local", sender=None):
|
||||
"""Convenience function to share an activity with other buddies."""
|
||||
if not util.validate_activity_id(activity_id):
|
||||
raise ValueError("invalid activity id")
|
||||
owner_nick = self._owner.get_name()
|
||||
real_name = Service.compose_service_name(owner_nick, activity_id)
|
||||
if address and not isinstance(address, unicode):
|
||||
raise ValueError("address must be a unicode string.")
|
||||
if address == None and stype.endswith('_udp'):
|
||||
# Use random currently unassigned multicast address
|
||||
address = u"232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254),
|
||||
random.randint(1, 254))
|
||||
properties['address'] = address
|
||||
properties['port'] = port
|
||||
if port and port != -1 and (not isinstance(port, int) or port <= 1024 or port >= 65535):
|
||||
raise ValueError("port must be a number between 1024 and 65535")
|
||||
|
||||
color = self._owner.get_color()
|
||||
if color:
|
||||
properties['color'] = color
|
||||
|
||||
logging.debug('Share activity %s, type %s, address %s, port %d, " \
|
||||
"properties %s' % (activity_id, stype, address, port,
|
||||
properties))
|
||||
return self.register_service(real_name, stype, properties, address,
|
||||
port, domain, sender)
|
||||
|
||||
def register_service(self, name, stype, properties={}, address=None,
|
||||
port=-1, domain=u"local", sender=None):
|
||||
"""Register a new service, advertising it to other Buddies on the network."""
|
||||
# Refuse to register if we can't get the dbus connection this request
|
||||
# came from for some reason
|
||||
if not sender:
|
||||
raise RuntimeError("Service registration request must have a sender.")
|
||||
|
||||
(actid, person_name) = Service.decompose_service_name(name)
|
||||
if self.get_owner() and person_name != self.get_owner().get_name():
|
||||
raise RuntimeError("Tried to register a service that didn't have" \
|
||||
" Owner nick as the service name!")
|
||||
if not domain or not len(domain):
|
||||
domain = u"local"
|
||||
if not port or port == -1:
|
||||
port = random.randint(4000, 65000)
|
||||
|
||||
objid = self._get_next_object_id()
|
||||
service = Service.Service(self._bus_name, objid, name=name,
|
||||
stype=stype, domain=domain, address=address, port=port,
|
||||
properties=properties, source_address=None,
|
||||
local_publisher=sender)
|
||||
self._services[(name, stype)] = service
|
||||
self.register_service_type(stype)
|
||||
service.register(self._system_bus, self._mdns_service)
|
||||
return service
|
||||
|
||||
def unregister_service(self, service, sender=None):
|
||||
service.unregister(sender)
|
||||
|
||||
def register_service_type(self, stype):
|
||||
"""Requests that the Presence service look for and recognize
|
||||
a certain mDNS service types."""
|
||||
if not isinstance(stype, unicode):
|
||||
raise ValueError("service type must be a unicode string.")
|
||||
|
||||
# If we've already registered it as a service type, ref it and return
|
||||
item = self._find_registered_service_type(stype)
|
||||
if item is not None:
|
||||
item.ref()
|
||||
return
|
||||
|
||||
# Otherwise track this type now
|
||||
obj = RegisteredServiceType(stype)
|
||||
self._registered_service_types.append(obj)
|
||||
|
||||
# Find unresolved services that match the service type
|
||||
# we're now interested in, and resolve them
|
||||
resolv_list = []
|
||||
|
||||
# Find services of this type
|
||||
resolv_list = self._find_service_adv(stype=stype)
|
||||
# Request resolution for them if they aren't in-process already
|
||||
for adv in resolv_list:
|
||||
if adv.state() == _SA_UNRESOLVED:
|
||||
adv.set_state(_SA_RESOLVE_PENDING)
|
||||
gobject.idle_add(self._resolve_service, adv)
|
||||
|
||||
def unregister_service_type(self, stype):
|
||||
"""Stop tracking a certain mDNS service."""
|
||||
if not isinstance(stype, unicode):
|
||||
raise ValueError("service type must be a unicode string.")
|
||||
|
||||
# if it was found, unref it and possibly remove it
|
||||
item = self._find_registered_service_type(stype)
|
||||
if not item:
|
||||
return
|
||||
if item.unref() <= 0:
|
||||
self._registered_service_types.remove(item)
|
||||
del item
|
||||
|
||||
|
||||
def main():
|
||||
loop = gobject.MainLoop()
|
||||
ps = PresenceService()
|
||||
try:
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
print 'Ctrl+C pressed, exiting...'
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,602 +0,0 @@
|
||||
# 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 avahi
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath("../../"))
|
||||
from sugar import util
|
||||
import dbus, dbus.service
|
||||
import random
|
||||
import logging
|
||||
import gobject
|
||||
|
||||
def compose_service_name(name, activity_id):
|
||||
if isinstance(name, str):
|
||||
name = unicode(name)
|
||||
if not name:
|
||||
raise ValueError("name must be a valid string.")
|
||||
if not activity_id:
|
||||
return name
|
||||
if not isinstance(name, unicode):
|
||||
raise ValueError("name must be in unicode.")
|
||||
composed = "%s [%s]" % (name, activity_id)
|
||||
return composed
|
||||
|
||||
def decompose_service_name(name):
|
||||
"""Break a service name into the name and activity ID, if we can."""
|
||||
if not isinstance(name, unicode):
|
||||
raise ValueError("name must be a valid unicode string.")
|
||||
name_len = len(name)
|
||||
if name_len < util.ACTIVITY_ID_LEN + 5:
|
||||
return (None, name)
|
||||
# check for activity id end marker
|
||||
if name[name_len - 1] != "]":
|
||||
return (None, name)
|
||||
start = name_len - 1 - util.ACTIVITY_ID_LEN
|
||||
end = name_len - 1
|
||||
# check for activity id start marker
|
||||
if name[start - 1] != "[" or name[start - 2] != " ":
|
||||
return (None, name)
|
||||
activity_id = name[start:end]
|
||||
if not util.validate_activity_id(activity_id):
|
||||
return (None, name)
|
||||
return (activity_id, name[:start - 2])
|
||||
|
||||
def _one_dict_differs(dict1, dict2):
|
||||
diff_keys = []
|
||||
for key, value in dict1.items():
|
||||
if not dict2.has_key(key) or dict2[key] != value:
|
||||
diff_keys.append(key)
|
||||
return diff_keys
|
||||
|
||||
def _dicts_differ(dict1, dict2):
|
||||
diff_keys = []
|
||||
diff1 = _one_dict_differs(dict1, dict2)
|
||||
diff2 = _one_dict_differs(dict2, dict1)
|
||||
for key in diff2:
|
||||
if key not in diff1:
|
||||
diff_keys.append(key)
|
||||
diff_keys += diff1
|
||||
return diff_keys
|
||||
|
||||
def _convert_properties_to_dbus_byte_array(props):
|
||||
# Ensure properties are converted to ByteArray types
|
||||
# because python sometimes can't figure that out
|
||||
info = dbus.Array([], signature="aay")
|
||||
for k, v in props.items():
|
||||
info.append(dbus.types.ByteArray("%s=%s" % (k, v)))
|
||||
return info
|
||||
|
||||
|
||||
_ACTIVITY_ID_TAG = "ActivityID"
|
||||
SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service"
|
||||
SERVICE_DBUS_OBJECT_PATH = "/org/laptop/Presence/Services/"
|
||||
|
||||
class ServiceDBusHelper(dbus.service.Object):
|
||||
"""Handle dbus requests and signals for Service objects"""
|
||||
def __init__(self, parent, bus_name, object_path):
|
||||
self._parent = parent
|
||||
self._bus_name = bus_name
|
||||
self._object_path = object_path
|
||||
dbus.service.Object.__init__(self, bus_name, self._object_path)
|
||||
|
||||
@dbus.service.signal(SERVICE_DBUS_INTERFACE,
|
||||
signature="as")
|
||||
def PublishedValueChanged(self, keylist):
|
||||
pass
|
||||
|
||||
@dbus.service.method(SERVICE_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="a{sv}")
|
||||
def getProperties(self):
|
||||
"""Return service properties."""
|
||||
pary = {}
|
||||
pary['name'] = self._parent.get_name()
|
||||
pary['type'] = self._parent.get_type()
|
||||
pary['domain'] = self._parent.get_domain()
|
||||
actid = self._parent.get_activity_id()
|
||||
if actid:
|
||||
pary['activityId'] = actid
|
||||
port = self._parent.get_port()
|
||||
if port:
|
||||
pary['port'] = self._parent.get_port()
|
||||
addr = self._parent.get_address()
|
||||
if addr:
|
||||
pary['address'] = addr
|
||||
source_addr = self._parent.get_source_address()
|
||||
if source_addr:
|
||||
pary['sourceAddress'] = source_addr
|
||||
return pary
|
||||
|
||||
@dbus.service.method(SERVICE_DBUS_INTERFACE,
|
||||
in_signature="s")
|
||||
def getPublishedValue(self, key):
|
||||
"""Return the value belonging to the requested key from the
|
||||
service's TXT records."""
|
||||
val = self._parent.get_one_property(key)
|
||||
if not val:
|
||||
raise KeyError("Value was not found.")
|
||||
return val
|
||||
|
||||
@dbus.service.method(SERVICE_DBUS_INTERFACE,
|
||||
in_signature="", out_signature="a{sv}")
|
||||
def getPublishedValues(self):
|
||||
pary = {}
|
||||
props = self._parent.get_properties()
|
||||
for key, value in props.items():
|
||||
pary[key] = str(value)
|
||||
return dbus.Dictionary(pary)
|
||||
|
||||
@dbus.service.method(SERVICE_DBUS_INTERFACE,
|
||||
sender_keyword="sender")
|
||||
def setPublishedValue(self, key, value, sender):
|
||||
self._parent.set_property(key, value, sender)
|
||||
|
||||
@dbus.service.method(SERVICE_DBUS_INTERFACE,
|
||||
in_signature="a{sv}", sender_keyword="sender")
|
||||
def setPublishedValues(self, values, sender):
|
||||
if not self._parent.is_local():
|
||||
raise ValueError("Service was not not registered by requesting process!")
|
||||
self._parent.set_properties(values, sender)
|
||||
|
||||
|
||||
class Service(gobject.GObject):
|
||||
"""Encapsulates information about a specific ZeroConf/mDNS
|
||||
service as advertised on the network."""
|
||||
|
||||
__gsignals__ = {
|
||||
'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT]))
|
||||
}
|
||||
|
||||
def __init__(self, bus_name, object_id, name, stype, domain=u"local",
|
||||
address=None, port=-1, properties=None, source_address=None,
|
||||
local_publisher=None):
|
||||
gobject.GObject.__init__(self)
|
||||
if not bus_name:
|
||||
raise ValueError("DBus bus name must be valid")
|
||||
if not object_id or not isinstance(object_id, int):
|
||||
raise ValueError("object id must be a valid number")
|
||||
|
||||
# Validate immutable options
|
||||
if name and not isinstance(name, unicode):
|
||||
raise ValueError("name must be unicode.")
|
||||
if not name or not len(name):
|
||||
raise ValueError("must specify a valid service name.")
|
||||
|
||||
if stype and not isinstance(stype, unicode):
|
||||
raise ValueError("service type must be in unicode.")
|
||||
if not stype or not len(stype):
|
||||
raise ValueError("must specify a valid service type.")
|
||||
if not stype.endswith("._tcp") and not stype.endswith("._udp"):
|
||||
raise ValueError("must specify a TCP or UDP service type.")
|
||||
|
||||
if not isinstance(domain, unicode):
|
||||
raise ValueError("domain must be in unicode.")
|
||||
if domain and domain != "local":
|
||||
raise ValueError("must use the 'local' domain (for now).")
|
||||
|
||||
# ID of the D-Bus connection that published this service, if any.
|
||||
# We only let the local publisher modify the service.
|
||||
self._local_publisher = local_publisher
|
||||
|
||||
self._avahi_entry_group = None
|
||||
|
||||
(actid, real_name) = decompose_service_name(name)
|
||||
self._name = real_name
|
||||
self._full_name = name
|
||||
self._stype = stype
|
||||
self._domain = domain
|
||||
self._port = -1
|
||||
self.set_port(port)
|
||||
self._properties = {}
|
||||
self._dbus_helper = None
|
||||
self._internal_set_properties(properties)
|
||||
|
||||
# Source address is the unicast source IP
|
||||
self._source_address = None
|
||||
if source_address is not None:
|
||||
self.set_source_address(source_address)
|
||||
|
||||
# Address is the published address, could be multicast or unicast
|
||||
self._address = None
|
||||
if self._properties.has_key('address'):
|
||||
self.set_address(self._properties['address'])
|
||||
elif address is not None:
|
||||
self.set_address(address)
|
||||
self._properties['address'] = address
|
||||
|
||||
# Ensure that an ActivityID tag, if given, matches
|
||||
# what we expect from the service type
|
||||
if self._properties.has_key(_ACTIVITY_ID_TAG):
|
||||
prop_actid = self._properties[_ACTIVITY_ID_TAG]
|
||||
if (prop_actid and not actid) or (prop_actid != actid):
|
||||
raise ValueError("ActivityID property specified, but the " \
|
||||
"service names's activity ID didn't match it: %s," \
|
||||
" %s" % (prop_actid, actid))
|
||||
self._activity_id = actid
|
||||
if actid and not self._properties.has_key(_ACTIVITY_ID_TAG):
|
||||
self._properties[_ACTIVITY_ID_TAG] = actid
|
||||
|
||||
self._owner = None
|
||||
|
||||
# register ourselves with dbus
|
||||
self._object_id = object_id
|
||||
self._object_path = SERVICE_DBUS_OBJECT_PATH + str(self._object_id)
|
||||
self._dbus_helper = ServiceDBusHelper(self, bus_name, self._object_path)
|
||||
|
||||
def object_path(self):
|
||||
return dbus.ObjectPath(self._object_path)
|
||||
|
||||
def get_owner(self):
|
||||
return self._owner
|
||||
|
||||
def set_owner(self, owner):
|
||||
if self._owner is not None:
|
||||
raise RuntimeError("Can only set a service's owner once")
|
||||
self._owner = owner
|
||||
|
||||
def is_local(self):
|
||||
if self._local_publisher is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_name(self):
|
||||
"""Return the service's name, usually that of the
|
||||
buddy who provides it."""
|
||||
return self._name
|
||||
|
||||
def get_full_name(self):
|
||||
return self._full_name
|
||||
|
||||
def get_one_property(self, key):
|
||||
"""Return one property of the service, or None
|
||||
if the property was not found. Cannot distinguish
|
||||
between lack of a property, and a property value that
|
||||
actually is None."""
|
||||
if key in self._properties.keys():
|
||||
return self._properties[key]
|
||||
return None
|
||||
|
||||
def get_properties(self):
|
||||
"""Return a python dictionary of all the service's
|
||||
properties."""
|
||||
return self._properties
|
||||
|
||||
def __emit_properties_changed_signal(self, keys):
|
||||
if self._dbus_helper:
|
||||
self._dbus_helper.PublishedValueChanged(keys)
|
||||
self.emit('property-changed', keys)
|
||||
|
||||
def set_property(self, key, value, sender=None):
|
||||
"""Set one service property"""
|
||||
if not self._local_publisher:
|
||||
raise ValueError("Service was not not registered by requesting process!")
|
||||
if sender is not None and self._local_publisher != sender:
|
||||
raise ValueError("Service was not not registered by requesting process!")
|
||||
|
||||
if not isinstance(key, unicode):
|
||||
raise ValueError("Key must be a unicode string.")
|
||||
if not isinstance(value, unicode) and not isinstance(value, bool):
|
||||
raise ValueError("Key must be a unicode string or a boolean.")
|
||||
|
||||
# Ignore setting the key to it's current value
|
||||
if self._properties.has_key(key):
|
||||
if self._properties[key] == value:
|
||||
return
|
||||
|
||||
# Blank value means remove key
|
||||
remove = False
|
||||
if isinstance(value, unicode) and len(value) == 0:
|
||||
remove = True
|
||||
if isinstance(value, bool) and value == False:
|
||||
remove = True
|
||||
|
||||
if remove:
|
||||
# If the key wasn't present, return without error
|
||||
if self._properties.has_key(key):
|
||||
del self._properties[key]
|
||||
else:
|
||||
# Otherwise set it
|
||||
if isinstance(value, bool):
|
||||
value = ""
|
||||
self._properties[key] = value
|
||||
|
||||
# if the service is locally published already, update the TXT records
|
||||
if self._local_publisher and self._avahi_entry_group:
|
||||
self.__internal_update_avahi_properties()
|
||||
|
||||
self.__emit_properties_changed_signal([key])
|
||||
|
||||
def set_properties(self, properties, sender=None, from_network=False):
|
||||
"""Set all service properties in one call"""
|
||||
if sender is not None and self._local_publisher != sender:
|
||||
raise ValueError("Service was not not registered by requesting process!")
|
||||
|
||||
self._internal_set_properties(properties, from_network)
|
||||
|
||||
def _internal_set_properties(self, properties, from_network=False):
|
||||
"""Set the service's properties from either an Avahi
|
||||
TXT record (a list of lists of integers), or a
|
||||
python dictionary."""
|
||||
if not isinstance (properties, dict):
|
||||
raise ValueError("Properties must be a dictionary.")
|
||||
|
||||
# Make sure the properties are actually different
|
||||
diff_keys = _dicts_differ(self._properties, properties)
|
||||
if len(diff_keys) == 0:
|
||||
return
|
||||
|
||||
self._properties = {}
|
||||
# Set key/value pairs on internal property list
|
||||
for key, value in properties.items():
|
||||
if len(key) == 0:
|
||||
continue
|
||||
tmp_key = key
|
||||
tmp_val = value
|
||||
if not isinstance(tmp_key, unicode):
|
||||
tmp_key = unicode(tmp_key)
|
||||
if not isinstance(tmp_val, unicode):
|
||||
tmp_val = unicode(tmp_val)
|
||||
self._properties[tmp_key] = tmp_val
|
||||
|
||||
# if the service is locally published already, update the TXT records
|
||||
if self._local_publisher and self._avahi_entry_group and not from_network:
|
||||
self.__internal_update_avahi_properties()
|
||||
|
||||
self.__emit_properties_changed_signal(diff_keys)
|
||||
|
||||
def __internal_update_avahi_properties(self):
|
||||
info = _convert_properties_to_dbus_byte_array(self._properties)
|
||||
self._avahi_entry_group.UpdateServiceTxt(avahi.IF_UNSPEC,
|
||||
avahi.PROTO_UNSPEC, 0,
|
||||
dbus.String(self._full_name), dbus.String(self._stype),
|
||||
dbus.String(self._domain), info)
|
||||
|
||||
def get_type(self):
|
||||
"""Return the service's service type."""
|
||||
return self._stype
|
||||
|
||||
def get_activity_id(self):
|
||||
"""Return the activity ID this service is associated with, if any."""
|
||||
return self._activity_id
|
||||
|
||||
def get_port(self):
|
||||
return self._port
|
||||
|
||||
def set_port(self, port):
|
||||
if not isinstance(port, int) or (port <= 1024 and port > 65536):
|
||||
raise ValueError("must specify a valid port number between 1024 and 65536.")
|
||||
self._port = port
|
||||
|
||||
def get_source_address(self):
|
||||
return self._source_address
|
||||
|
||||
def set_source_address(self, address):
|
||||
if not address or not isinstance(address, unicode):
|
||||
raise ValueError("address must be unicode")
|
||||
self._source_address = address
|
||||
|
||||
def get_address(self):
|
||||
return self._address
|
||||
|
||||
def set_address(self, address):
|
||||
if not address or not isinstance(address, unicode):
|
||||
raise ValueError("address must be a unicode string")
|
||||
self._address = address
|
||||
self._properties['address'] = address
|
||||
|
||||
def get_domain(self):
|
||||
"""Return the ZeroConf/mDNS domain the service was found in."""
|
||||
return self._domain
|
||||
|
||||
def register(self, system_bus, avahi_service):
|
||||
if self._avahi_entry_group is not None:
|
||||
raise RuntimeError("Service already registered!")
|
||||
|
||||
obj = system_bus.get_object(avahi.DBUS_NAME, avahi_service.EntryGroupNew())
|
||||
self._avahi_entry_group = dbus.Interface(obj, avahi.DBUS_INTERFACE_ENTRY_GROUP)
|
||||
|
||||
info = _convert_properties_to_dbus_byte_array(self._properties)
|
||||
logging.debug("Will register service with name='%s', stype='%s'," \
|
||||
" domain='%s', address='%s', port=%d, info='%s'" % (self._full_name,
|
||||
self._stype, self._domain, self._address, self._port, info))
|
||||
|
||||
self._avahi_entry_group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0,
|
||||
dbus.String(self._full_name), dbus.String(self._stype),
|
||||
dbus.String(self._domain), dbus.String(""), # let Avahi figure the 'host' out
|
||||
dbus.UInt16(self._port), info)
|
||||
|
||||
self._avahi_entry_group.connect_to_signal('StateChanged', self.__entry_group_changed_cb)
|
||||
self._avahi_entry_group.Commit()
|
||||
|
||||
def __entry_group_changed_cb(self, state, error):
|
||||
pass
|
||||
# logging.debug("** %s.%s Entry group changed: state %s, error %s" % (self._full_name, self._stype, state, error))
|
||||
|
||||
def unregister(self, sender):
|
||||
# Refuse to unregister if we can't get the dbus connection this request
|
||||
# came from for some reason
|
||||
if not sender:
|
||||
raise RuntimeError("Service registration request must have a sender.")
|
||||
if not self._local_publisher:
|
||||
raise ValueError("Service was not a local service provided by this laptop!")
|
||||
if sender is not None and self._local_publisher != sender:
|
||||
raise ValueError("Service was not registered by requesting process!")
|
||||
if not self._avahi_entry_group:
|
||||
raise ValueError("Service was not registered by requesting process!")
|
||||
self._avahi_entry_group.Free()
|
||||
del self._avahi_entry_group
|
||||
self._avahi_entry_group = None
|
||||
|
||||
|
||||
#################################################################
|
||||
# Tests
|
||||
#################################################################
|
||||
|
||||
import unittest
|
||||
|
||||
__objid_seq = 0
|
||||
def _next_objid():
|
||||
global __objid_seq
|
||||
__objid_seq = __objid_seq + 1
|
||||
return __objid_seq
|
||||
|
||||
|
||||
class ServiceTestCase(unittest.TestCase):
|
||||
_DEF_NAME = u"foobar"
|
||||
_DEF_STYPE = u"_foo._bar._tcp"
|
||||
_DEF_DOMAIN = u"local"
|
||||
_DEF_ADDRESS = u"1.1.1.1"
|
||||
_DEF_PORT = 1234
|
||||
_DEF_PROPS = {'foobar': 'baz'}
|
||||
_STR_TEST_ARGS = [None, 0, [], {}]
|
||||
|
||||
def __init__(self, name):
|
||||
self._bus = dbus.SessionBus()
|
||||
self._bus_name = dbus.service.BusName('org.laptop.Presence', bus=self._bus)
|
||||
unittest.TestCase.__init__(self, name)
|
||||
|
||||
def __del__(self):
|
||||
del self._bus_name
|
||||
del self._bus
|
||||
|
||||
def _test_init_fail(self, name, stype, domain, address, port, properties, fail_msg):
|
||||
"""Test something we expect to fail."""
|
||||
try:
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, name, stype, domain, address,
|
||||
port, properties)
|
||||
except ValueError, exc:
|
||||
pass
|
||||
else:
|
||||
self.fail("expected a ValueError for %s." % fail_msg)
|
||||
|
||||
def testName(self):
|
||||
for item in self._STR_TEST_ARGS:
|
||||
self._test_init_fail(item, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid name")
|
||||
|
||||
def testType(self):
|
||||
for item in self._STR_TEST_ARGS:
|
||||
self._test_init_fail(self._DEF_NAME, item, self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid service type")
|
||||
self._test_init_fail(self._DEF_NAME, u"_bork._foobar", self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid service type")
|
||||
|
||||
def testDomain(self):
|
||||
for item in self._STR_TEST_ARGS:
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, item, self._DEF_ADDRESS,
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid domain")
|
||||
# Only accept local for now
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, u"foobar", self._DEF_ADDRESS,
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid domain")
|
||||
# Make sure "" works
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, u"",
|
||||
self._DEF_ADDRESS, self._DEF_PORT, self._DEF_PROPS)
|
||||
assert service, "Empty domain was not accepted!"
|
||||
|
||||
def testAddress(self):
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, [],
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid address")
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, {},
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid address")
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, 1234,
|
||||
self._DEF_PORT, self._DEF_PROPS, "invalid address")
|
||||
|
||||
def testPort(self):
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
[], self._DEF_PROPS, "invalid port")
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
{}, self._DEF_PROPS, "invalid port")
|
||||
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
"adf", self._DEF_PROPS, "invalid port")
|
||||
|
||||
def testGoodInit(self):
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
|
||||
self._DEF_ADDRESS, self._DEF_PORT, self._DEF_PROPS)
|
||||
assert service.get_name() == self._DEF_NAME, "service name wasn't correct after init."
|
||||
assert service.get_type() == self._DEF_STYPE, "service type wasn't correct after init."
|
||||
assert service.get_domain() == "local", "service domain wasn't correct after init."
|
||||
assert service.get_address() == self._DEF_ADDRESS, "service address wasn't correct after init."
|
||||
assert service.get_port() == self._DEF_PORT, "service port wasn't correct after init."
|
||||
assert service.object_path() == SERVICE_DBUS_OBJECT_PATH + str(objid)
|
||||
value = service.get_one_property('foobar')
|
||||
assert value and value == 'baz', "service property wasn't correct after init."
|
||||
|
||||
def testAvahiProperties(self):
|
||||
props = [[111, 114, 103, 46, 102, 114, 101, 101, 100, 101, 115, 107, 116, 111, 112, 46, 65, 118, 97, 104, 105, 46, 99, 111, 111, 107, 105, 101, 61, 50, 54, 48, 49, 53, 52, 51, 57, 53, 50]]
|
||||
key = "org.freedesktop.Avahi.cookie"
|
||||
expected_value = "2601543952"
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
|
||||
self._DEF_ADDRESS, self._DEF_PORT, props)
|
||||
value = service.get_one_property(key)
|
||||
assert value and value == expected_value, "service properties weren't correct after init."
|
||||
value = service.get_one_property('bork')
|
||||
assert not value, "service properties weren't correct after init."
|
||||
|
||||
def testBoolProperty(self):
|
||||
props = [[111, 114, 103, 46, 102, 114, 101, 101, 100, 101, 115, 107, 116, 111, 112, 46, 65, 118, 97, 104, 105, 46, 99, 111, 111, 107, 105, 101]]
|
||||
key = "org.freedesktop.Avahi.cookie"
|
||||
expected_value = True
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
|
||||
self._DEF_PORT, props)
|
||||
value = service.get_one_property(key)
|
||||
assert value is not None and value == expected_value, "service properties weren't correct after init."
|
||||
|
||||
def testActivityService(self):
|
||||
# Valid group service type, non-multicast address
|
||||
actid = "4569a71b80805aa96a847f7ac1c407327b3ec2b4"
|
||||
name = compose_service_name("Tommy", actid)
|
||||
|
||||
# Valid activity service name, None address
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, None,
|
||||
self._DEF_PORT, self._DEF_PROPS)
|
||||
assert service.get_address() == None, "address was not None as expected!"
|
||||
assert service.get_activity_id() == actid, "activity id was different than expected!"
|
||||
|
||||
# Valid activity service name and multicast address, ensure it works
|
||||
mc_addr = u"224.0.0.34"
|
||||
objid = _next_objid()
|
||||
service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, mc_addr,
|
||||
self._DEF_PORT, self._DEF_PROPS)
|
||||
assert service.get_address() == mc_addr, "address was not expected address!"
|
||||
|
||||
def addToSuite(suite):
|
||||
suite.addTest(ServiceTestCase("testName"))
|
||||
suite.addTest(ServiceTestCase("testType"))
|
||||
suite.addTest(ServiceTestCase("testDomain"))
|
||||
suite.addTest(ServiceTestCase("testAddress"))
|
||||
suite.addTest(ServiceTestCase("testPort"))
|
||||
suite.addTest(ServiceTestCase("testGoodInit"))
|
||||
suite.addTest(ServiceTestCase("testAvahiProperties"))
|
||||
suite.addTest(ServiceTestCase("testBoolProperty"))
|
||||
suite.addTest(ServiceTestCase("testActivityService"))
|
||||
addToSuite = staticmethod(addToSuite)
|
||||
|
||||
|
||||
def main():
|
||||
suite = unittest.TestSuite()
|
||||
ServiceTestCase.addToSuite(suite)
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(suite)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,258 @@
|
||||
# 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, dbus.service
|
||||
from sugar import util
|
||||
|
||||
from telepathy.interfaces import (CHANNEL_INTERFACE)
|
||||
|
||||
_ACTIVITY_PATH = "/org/laptop/Sugar/Presence/Activities/"
|
||||
_ACTIVITY_INTERFACE = "org.laptop.Sugar.Presence.Activity"
|
||||
|
||||
class DBusGObjectMetaclass(gobject.GObjectMeta, dbus.service.InterfaceType): pass
|
||||
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
|
||||
|
||||
|
||||
class Activity(DBusGObject):
|
||||
__gtype_name__ = "Activity"
|
||||
|
||||
__gsignals__ = {
|
||||
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_BOOLEAN]))
|
||||
}
|
||||
|
||||
__gproperties__ = {
|
||||
'id' : (str, None, None, None,
|
||||
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
|
||||
'name' : (str, None, None, None, gobject.PARAM_READWRITE),
|
||||
'color' : (str, None, None, None, gobject.PARAM_READWRITE),
|
||||
'type' : (str, None, None, None, gobject.PARAM_READWRITE),
|
||||
'valid' : (bool, None, None, False, gobject.PARAM_READABLE),
|
||||
'local' : (bool, None, None, False,
|
||||
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
|
||||
'joined' : (bool, None, None, False, gobject.PARAM_READABLE)
|
||||
}
|
||||
|
||||
def __init__(self, bus_name, object_id, tp, **kwargs):
|
||||
if not bus_name:
|
||||
raise ValueError("DBus bus name must be valid")
|
||||
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._object_id = object_id
|
||||
self._object_path = _ACTIVITY_PATH + str(self._object_id)
|
||||
dbus.service.Object.__init__(self, bus_name, self._object_path)
|
||||
|
||||
self._buddies = []
|
||||
self._joined = False
|
||||
|
||||
# the telepathy client
|
||||
self._tp = tp
|
||||
self._activity_text_channel = None
|
||||
|
||||
self._valid = False
|
||||
self._id = None
|
||||
self._color = None
|
||||
self._local = False
|
||||
self._type = None
|
||||
|
||||
if not kwargs.get("id"):
|
||||
raise ValueError("activity id is required")
|
||||
if not util.validate_activity_id(kwargs['id']):
|
||||
raise ValueError("Invalid activity id '%s'" % kwargs['id'])
|
||||
|
||||
gobject.GObject.__init__(self, **kwargs)
|
||||
if self.props.local and not self.props.valid:
|
||||
raise RuntimeError("local activities require color, type, and name")
|
||||
|
||||
def do_get_property(self, pspec):
|
||||
if pspec.name == "id":
|
||||
return self._id
|
||||
elif pspec.name == "name":
|
||||
return self._name
|
||||
elif pspec.name == "color":
|
||||
return self._color
|
||||
elif pspec.name == "type":
|
||||
return self._type
|
||||
elif pspec.name == "valid":
|
||||
return self._valid
|
||||
elif pspec.name == "joined":
|
||||
return self._joined
|
||||
elif pspec.name == "local":
|
||||
return self._local
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
if pspec.name == "id":
|
||||
self._id = value
|
||||
elif pspec.name == "name":
|
||||
self._name = value
|
||||
elif pspec.name == "color":
|
||||
self._color = value
|
||||
elif pspec.name == "type":
|
||||
if self._type:
|
||||
raise RuntimeError("activity type is already set")
|
||||
self._type = value
|
||||
elif pspec.name == "joined":
|
||||
self._joined = value
|
||||
elif pspec.name == "local":
|
||||
self._local = value
|
||||
|
||||
self._update_validity()
|
||||
|
||||
def _update_validity(self):
|
||||
try:
|
||||
old_valid = self._valid
|
||||
if self._color and self._name 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):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_ACTIVITY_INTERFACE,
|
||||
signature="o")
|
||||
def BuddyLeft(self, buddy_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_ACTIVITY_INTERFACE,
|
||||
signature="o")
|
||||
def NewChannel(self, channel_path):
|
||||
pass
|
||||
|
||||
# dbus methods
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="", out_signature="s")
|
||||
def GetId(self):
|
||||
return self.props.id
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="", out_signature="s")
|
||||
def GetColor(self):
|
||||
return self.props.color
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="", out_signature="s")
|
||||
def GetType(self):
|
||||
return self.props.type
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="", out_signature="")
|
||||
def Join(self):
|
||||
self.join()
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="", out_signature="ao")
|
||||
def GetJoinedBuddies(self):
|
||||
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):
|
||||
return self.get_channels()
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="", out_signature="s")
|
||||
def GetName(self):
|
||||
return self.props.name
|
||||
|
||||
# methods
|
||||
def object_path(self):
|
||||
return dbus.ObjectPath(self._object_path)
|
||||
|
||||
def get_joined_buddies(self):
|
||||
ret = []
|
||||
for buddy in self._buddies:
|
||||
if buddy.props.valid:
|
||||
ret.append(buddy)
|
||||
return ret
|
||||
|
||||
def buddy_joined(self, buddy):
|
||||
if buddy not in self._buddies:
|
||||
self._buddies.append(buddy)
|
||||
if self.props.valid:
|
||||
self.BuddyJoined(buddy.object_path())
|
||||
|
||||
def buddy_left(self, buddy):
|
||||
if buddy in self._buddies:
|
||||
self._buddies.remove(buddy)
|
||||
if self.props.valid:
|
||||
self.BuddyLeft(buddy.object_path())
|
||||
|
||||
def join(self):
|
||||
if not self._joined:
|
||||
self._activity_text_channel = self._tp.join_activity(self.props.id)
|
||||
self._activity_text_channel[CHANNEL_INTERFACE].connect_to_signal('Closed', self._activity_text_channel_closed_cb)
|
||||
self._joined = True
|
||||
|
||||
def get_channels(self):
|
||||
conn = self._tp.get_connection()
|
||||
# FIXME add tubes and others channels
|
||||
return str(conn.service_name), conn.object_path, [self._activity_text_channel.object_path]
|
||||
|
||||
def leave(self):
|
||||
if self._joined:
|
||||
self._activity_text_channel[CHANNEL_INTERFACE].Close()
|
||||
|
||||
def _activity_text_channel_closed_cb(self):
|
||||
self._joined = False
|
||||
self._activity_text_channel = None
|
||||
|
||||
def send_properties(self):
|
||||
props = {}
|
||||
props['name'] = self._name
|
||||
props['color'] = self._color
|
||||
props['type'] = self._type
|
||||
self._tp.set_activity_properties(self.props.id, props)
|
||||
|
||||
def set_properties(self, properties):
|
||||
changed = False
|
||||
if "name" in properties.keys():
|
||||
name = properties["name"]
|
||||
if name != self._name:
|
||||
self._name = name
|
||||
changed = True
|
||||
|
||||
if "color" in properties.keys():
|
||||
color = properties["color"]
|
||||
if color != self._color:
|
||||
self._color = color
|
||||
changed = True
|
||||
|
||||
if "type" in properties.keys():
|
||||
type = properties["type"]
|
||||
if type != self._type:
|
||||
self._type = type
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self._update_validity()
|
||||
@@ -0,0 +1,396 @@
|
||||
# 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, dbus.service
|
||||
|
||||
from sugar import profile
|
||||
from sugar import env
|
||||
|
||||
_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/"
|
||||
_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
|
||||
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"
|
||||
|
||||
class NotFoundError(dbus.DBusException):
|
||||
def __init__(self):
|
||||
dbus.DBusException.__init__(self)
|
||||
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
|
||||
|
||||
class DBusGObjectMetaclass(gobject.GObjectMeta, dbus.service.InterfaceType): pass
|
||||
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
|
||||
|
||||
|
||||
class Buddy(DBusGObject):
|
||||
"""Represents another person on the network and keeps track of the
|
||||
activities and resources they make available for sharing."""
|
||||
|
||||
__gtype_name__ = "Buddy"
|
||||
|
||||
__gsignals__ = {
|
||||
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_BOOLEAN])),
|
||||
'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT]))
|
||||
}
|
||||
|
||||
__gproperties__ = {
|
||||
'key' : (str, None, None, None,
|
||||
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
|
||||
'icon' : (object, None, None, gobject.PARAM_READWRITE),
|
||||
'nick' : (str, None, None, None, gobject.PARAM_READWRITE),
|
||||
'color' : (str, None, None, None, gobject.PARAM_READWRITE),
|
||||
'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE),
|
||||
'valid' : (bool, None, None, False, gobject.PARAM_READABLE),
|
||||
'owner' : (bool, None, None, False, gobject.PARAM_READABLE)
|
||||
}
|
||||
|
||||
def __init__(self, bus_name, object_id, **kwargs):
|
||||
if not bus_name:
|
||||
raise ValueError("DBus bus name must be valid")
|
||||
if not object_id or not isinstance(object_id, int):
|
||||
raise ValueError("object id must be a valid number")
|
||||
|
||||
self._bus_name = bus_name
|
||||
self._object_id = object_id
|
||||
self._object_path = _BUDDY_PATH + str(self._object_id)
|
||||
dbus.service.Object.__init__(self, self._bus_name, self._object_path)
|
||||
|
||||
self._activities = {} # Activity ID -> Activity
|
||||
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
|
||||
|
||||
if not kwargs.get("key"):
|
||||
raise ValueError("key required")
|
||||
|
||||
gobject.GObject.__init__(self, **kwargs)
|
||||
|
||||
def do_get_property(self, pspec):
|
||||
if pspec.name == "key":
|
||||
return self._key
|
||||
elif pspec.name == "icon":
|
||||
return self._icon
|
||||
elif pspec.name == "nick":
|
||||
return self._nick
|
||||
elif pspec.name == "color":
|
||||
return self._color
|
||||
elif pspec.name == "current-activity":
|
||||
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 == "valid":
|
||||
return self._valid
|
||||
elif pspec.name == "owner":
|
||||
return self._owner
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
if pspec.name == "icon":
|
||||
if str(value) != self._icon:
|
||||
self._icon = str(value)
|
||||
self.IconChanged(self._icon)
|
||||
self.emit('icon-changed', self._icon)
|
||||
elif pspec.name == "nick":
|
||||
self._nick = value
|
||||
elif pspec.name == "color":
|
||||
self._color = value
|
||||
elif pspec.name == "current-activity":
|
||||
self._current_activity = value
|
||||
elif pspec.name == "key":
|
||||
self._key = value
|
||||
|
||||
self._update_validity()
|
||||
|
||||
# dbus signals
|
||||
@dbus.service.signal(_BUDDY_INTERFACE,
|
||||
signature="ay")
|
||||
def IconChanged(self, icon_data):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_BUDDY_INTERFACE,
|
||||
signature="o")
|
||||
def JoinedActivity(self, activity_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_BUDDY_INTERFACE,
|
||||
signature="o")
|
||||
def LeftActivity(self, activity_path):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(_BUDDY_INTERFACE,
|
||||
signature="a{sv}")
|
||||
def PropertyChanged(self, updated):
|
||||
pass
|
||||
|
||||
# dbus methods
|
||||
@dbus.service.method(_BUDDY_INTERFACE,
|
||||
in_signature="", out_signature="ay")
|
||||
def GetIcon(self):
|
||||
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):
|
||||
acts = []
|
||||
for act in self.get_joined_activities():
|
||||
acts.append(act.object_path())
|
||||
return acts
|
||||
|
||||
@dbus.service.method(_BUDDY_INTERFACE,
|
||||
in_signature="", out_signature="a{sv}")
|
||||
def GetProperties(self):
|
||||
props = {}
|
||||
props['nick'] = self.props.nick
|
||||
props['owner'] = self.props.owner
|
||||
props['key'] = self.props.key
|
||||
props['color'] = self.props.color
|
||||
return props
|
||||
|
||||
# methods
|
||||
def object_path(self):
|
||||
return dbus.ObjectPath(self._object_path)
|
||||
|
||||
def add_activity(self, activity):
|
||||
actid = activity.props.id
|
||||
if self._activities.has_key(actid):
|
||||
return
|
||||
self._activities[actid] = activity
|
||||
if activity.props.valid:
|
||||
self.JoinedActivity(activity.object_path())
|
||||
|
||||
def remove_activity(self, activity):
|
||||
actid = activity.props.id
|
||||
if not self._activities.has_key(actid):
|
||||
return
|
||||
del self._activities[actid]
|
||||
if activity.props.valid:
|
||||
self.LeftActivity(activity.object_path())
|
||||
|
||||
def get_joined_activities(self):
|
||||
acts = []
|
||||
for act in self._activities.values():
|
||||
if act.props.valid:
|
||||
acts.append(act)
|
||||
return acts
|
||||
|
||||
def set_properties(self, properties):
|
||||
changed = False
|
||||
if "nick" in properties.keys():
|
||||
nick = properties["nick"]
|
||||
if nick != self._nick:
|
||||
self._nick = nick
|
||||
changed = True
|
||||
if "color" in properties.keys():
|
||||
color = properties["color"]
|
||||
if color != self._color:
|
||||
self._color = color
|
||||
changed = True
|
||||
if "current-activity" in properties.keys():
|
||||
curact = properties["current-activity"]
|
||||
if curact != self._current_activity:
|
||||
self._current_activity = curact
|
||||
changed = True
|
||||
|
||||
if not changed:
|
||||
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:
|
||||
self.PropertyChanged(properties)
|
||||
self.emit('property-changed', properties)
|
||||
|
||||
self._update_validity()
|
||||
|
||||
def _update_validity(self):
|
||||
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 Owner(Buddy):
|
||||
"""Class representing the owner of the machine. This is the client
|
||||
portion of the Owner, paired with the server portion in Owner.py."""
|
||||
|
||||
_SHELL_SERVICE = "org.laptop.Shell"
|
||||
_SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
|
||||
_SHELL_PATH = "/org/laptop/Shell"
|
||||
|
||||
def __init__(self, bus_name, object_id):
|
||||
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()
|
||||
|
||||
self._bus = dbus.SessionBus()
|
||||
self._bus.add_signal_receiver(self._name_owner_changed_handler,
|
||||
signal_name="NameOwnerChanged",
|
||||
dbus_interface="org.freedesktop.DBus")
|
||||
|
||||
# Connect to the shell to get notifications on Owner object
|
||||
# property changes
|
||||
try:
|
||||
self._connect_to_shell()
|
||||
except dbus.DBusException:
|
||||
pass
|
||||
|
||||
Buddy.__init__(self, bus_name, object_id, key=key, nick=nick, color=color,
|
||||
icon=icon)
|
||||
self._owner = True
|
||||
|
||||
# enable this to change random buddy properties
|
||||
if False:
|
||||
gobject.timeout_add(5000, self._update_something)
|
||||
|
||||
def _update_something(self):
|
||||
import random
|
||||
it = random.randint(0, 3)
|
||||
if it == 0:
|
||||
data = get_random_image()
|
||||
self._icon_changed_cb(data)
|
||||
elif it == 1:
|
||||
from sugar.graphics import xocolor
|
||||
xo = xocolor.XoColor()
|
||||
self._color_changed_cb(xo.to_string())
|
||||
elif it == 2:
|
||||
names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
|
||||
foo = random.randint(0, len(names) - 1)
|
||||
self._nick_changed_cb(names[foo])
|
||||
elif it == 3:
|
||||
bork = random.randint(25, 65)
|
||||
it = ""
|
||||
for i in range(0, bork):
|
||||
it += chr(random.randint(40, 127))
|
||||
from sugar import util
|
||||
self._cur_activity_changed_cb(util.unique_id(it))
|
||||
return True
|
||||
|
||||
def _name_owner_changed_handler(self, name, old, new):
|
||||
if name != self._SHELL_SERVICE:
|
||||
return
|
||||
if (old and len(old)) and (not new and not len(new)):
|
||||
# shell went away
|
||||
self._shell_owner = None
|
||||
elif (not old and not len(old)) and (new and len(new)):
|
||||
# shell started
|
||||
self._connect_to_shell()
|
||||
|
||||
def _connect_to_shell(self):
|
||||
obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
|
||||
self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
|
||||
self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
|
||||
self._shell_owner.connect_to_signal('ColorChanged', self._color_changed_cb)
|
||||
self._shell_owner.connect_to_signal('NickChanged', self._nick_changed_cb)
|
||||
self._shell_owner.connect_to_signal('CurrentActivityChanged',
|
||||
self._cur_activity_changed_cb)
|
||||
|
||||
def _icon_changed_cb(self, icon):
|
||||
self.props.icon = icon
|
||||
|
||||
def _color_changed_cb(self, color):
|
||||
props = {'color': color}
|
||||
self.set_properties(props)
|
||||
|
||||
def _nick_changed_cb(self, nick):
|
||||
props = {'nick': nick}
|
||||
self.set_properties(props)
|
||||
|
||||
def _cur_activity_changed_cb(self, activity_id):
|
||||
if not self._activities.has_key(activity_id):
|
||||
# This activity is local-only
|
||||
activity_id = None
|
||||
props = {'current-activity': activity_id}
|
||||
self.set_properties(props)
|
||||
|
||||
|
||||
|
||||
def get_random_image():
|
||||
import cairo, math, random, 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])
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# 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
|
||||
from sugar import util
|
||||
|
||||
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")
|
||||
|
||||
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"
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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
|
||||
@@ -0,0 +1,332 @@
|
||||
# Copyright (C) 2007, 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 gobject
|
||||
import dbus, dbus.service, dbus.glib
|
||||
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, CONNECTION_HANDLE_TYPE_CONTACT)
|
||||
|
||||
from server_plugin import ServerPlugin
|
||||
from linklocal_plugin import LinkLocalPlugin
|
||||
from sugar import util
|
||||
|
||||
from buddy import Buddy, Owner
|
||||
from activity import Activity
|
||||
|
||||
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
|
||||
_PRESENCE_INTERFACE = "org.laptop.Sugar.Presence"
|
||||
_PRESENCE_PATH = "/org/laptop/Sugar/Presence"
|
||||
|
||||
|
||||
class NotFoundError(dbus.DBusException):
|
||||
def __init__(self):
|
||||
dbus.DBusException.__init__(self)
|
||||
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
|
||||
|
||||
|
||||
class PresenceService(dbus.service.Object):
|
||||
def __init__(self):
|
||||
self._next_object_id = 0
|
||||
|
||||
self._buddies = {} # key -> Buddy
|
||||
self._handles_buddies = {} # tp client -> (handle -> Buddy)
|
||||
self._activities = {} # activity id -> Activity
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=bus)
|
||||
|
||||
# Create the Owner object
|
||||
objid = self._get_next_object_id()
|
||||
self._owner = Owner(self._bus_name, objid)
|
||||
self._buddies[self._owner.props.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] = {}
|
||||
|
||||
dbus.service.Object.__init__(self, self._bus_name, _PRESENCE_PATH)
|
||||
|
||||
def _server_status_cb(self, plugin, status, reason):
|
||||
if status == CONNECTION_STATUS_CONNECTED:
|
||||
pass
|
||||
# TEST
|
||||
id = util.unique_id()
|
||||
self._share_activity(id, "org.laptop.Sugar.Test",
|
||||
"Chat of %s" % self._owner.props.nick, [])
|
||||
|
||||
def _contact_online(self, tp, handle, props):
|
||||
new_buddy = False
|
||||
key = props['key']
|
||||
buddy = self._buddies.get(key)
|
||||
if not buddy:
|
||||
# we don't know yet this buddy
|
||||
objid = self._get_next_object_id()
|
||||
buddy = Buddy(self._bus_name, objid, key=key)
|
||||
buddy.connect("validity-changed", self._buddy_validity_changed_cb)
|
||||
self._buddies[key] = buddy
|
||||
|
||||
buddies = self._handles_buddies[tp]
|
||||
buddies[handle] = buddy
|
||||
# store the handle of the buddy for this CM
|
||||
buddy.handles[tp] = handle
|
||||
|
||||
buddy.set_properties(props)
|
||||
|
||||
def _buddy_validity_changed_cb(self, buddy, valid):
|
||||
if valid:
|
||||
self.BuddyAppeared(buddy.object_path())
|
||||
print "New Buddy: %s (%s)" % (buddy.props.nick, buddy.props.color)
|
||||
else:
|
||||
self.BuddyDisappeared(buddy.object_path())
|
||||
print "Buddy left: %s (%s)" % (buddy.props.nick, buddy.props.color)
|
||||
|
||||
def _contact_offline(self, tp, handle):
|
||||
buddy = self._handles_buddies[tp].pop(handle)
|
||||
key = buddy.props.key
|
||||
|
||||
# the handle of the buddy for this CM is not valid anymore
|
||||
buddy.handles.pop(tp)
|
||||
if not buddy.handles:
|
||||
if buddy.props.valid:
|
||||
self.BuddyDisappeared(buddy.object_path())
|
||||
print "Buddy left: %s (%s)" % (buddy.props.nick, buddy.props.color)
|
||||
self._buddies.pop(key)
|
||||
|
||||
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:
|
||||
print "Buddy %s icon updated" % buddy.props.key
|
||||
buddy.props.icon = avatar
|
||||
|
||||
def _buddy_properties_changed(self, tp, handle, prop):
|
||||
buddy = self._handles_buddies[tp].get(handle)
|
||||
if buddy:
|
||||
buddy.set_properties(prop)
|
||||
#print "Buddy %s properties updated" % buddy.props.key
|
||||
|
||||
def _new_activity(self, activity_id, tp):
|
||||
try:
|
||||
objid = self._get_next_object_id()
|
||||
activity = Activity(self._bus_name, objid, tp, id=activity_id)
|
||||
except Exception, e:
|
||||
print "Invalid activity: %s" % e
|
||||
return None
|
||||
|
||||
activity.connect("validity-changed", self._activity_validity_changed_cb)
|
||||
|
||||
self._activities[activity_id] = activity
|
||||
|
||||
# FIXME
|
||||
# Use values from the network
|
||||
#import random
|
||||
#names = ["Tommy", "Susie", "Jill", "Bryan", "Nathan", "Sophia", "Haley", "Jimmy"]
|
||||
#name = names[random.randint(0, len(names) - 1)]
|
||||
#activity.props.name = "Chat with %s" % name
|
||||
#activity.props.type = "org.laptop.Sugar.Chat"
|
||||
#from sugar.graphics import xocolor
|
||||
#color = xocolor.XoColor().to_string()
|
||||
#activity.props.color = color
|
||||
|
||||
return activity
|
||||
|
||||
def _remove_activity(self, activity):
|
||||
print "remove activity", activity.props.id
|
||||
|
||||
self.ActivityDisappeared(activity.object_path())
|
||||
del self._activities[activity.props.id]
|
||||
|
||||
def _buddy_activities_changed(self, tp, contact_handle, activities):
|
||||
print "------------activities changed-------------"
|
||||
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?
|
||||
print "contact_activities_changed: buddy unknow"
|
||||
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:
|
||||
print "buddy", contact_handle, "joined", act
|
||||
activity = self._activities.get(act)
|
||||
if not activity:
|
||||
# new activity, can fail
|
||||
activity = self._new_activity(act, tp)
|
||||
|
||||
if activity:
|
||||
activity.buddy_joined(buddy)
|
||||
buddy.add_activity(activity)
|
||||
|
||||
activities_left = old_activities - new_activities
|
||||
for act in activities_left:
|
||||
print "buddy", contact_handle, "left", act
|
||||
activity = self._activities.get(act)
|
||||
if not activity:
|
||||
continue
|
||||
|
||||
activity.buddy_left(buddy)
|
||||
buddy.remove_activity(activity)
|
||||
|
||||
if not activity.get_joined_buddies():
|
||||
self._remove_activity(activity)
|
||||
|
||||
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, out_signature="ao")
|
||||
def GetActivities(self):
|
||||
ret = []
|
||||
for act in self._activities.values():
|
||||
ret.append(act.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", out_signature="o")
|
||||
def GetActivityById(self, actid):
|
||||
if self._activities.has_key(actid):
|
||||
return self._activities[actid].object_path()
|
||||
raise NotFoundError("The activity was not found.")
|
||||
|
||||
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao")
|
||||
def GetBuddies(self):
|
||||
ret = []
|
||||
for buddy in self._buddies.values():
|
||||
ret.append(buddy.object_path())
|
||||
return ret
|
||||
|
||||
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="ay", out_signature="o")
|
||||
def GetBuddyByPublicKey(self, key):
|
||||
if self._buddies.has_key(key):
|
||||
return self._buddies[key].object_path()
|
||||
raise NotFoundError("The buddy was not found.")
|
||||
|
||||
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="o")
|
||||
def GetOwner(self):
|
||||
if not self._owner:
|
||||
raise NotFoundError("The owner was not found.")
|
||||
else:
|
||||
return self._owner.get_object_path()
|
||||
|
||||
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="sssa{sv}", out_signature="o")
|
||||
def ShareActivity(self, actid, atype, name, properties):
|
||||
activity = self._share_activity(actid, atype, name, properties)
|
||||
return activity.object_path()
|
||||
|
||||
def cleanup(self):
|
||||
for tp in self._handles_buddies:
|
||||
tp.cleanup()
|
||||
|
||||
def _share_activity(self, actid, atype, name, properties):
|
||||
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._bus_name, objid, 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.join()
|
||||
activity.send_properties()
|
||||
|
||||
return activity
|
||||
|
||||
def _activity_validity_changed_cb(self, activity, valid):
|
||||
if valid:
|
||||
self.ActivityAppeared(activity.object_path())
|
||||
print "New Activity: %s (%s)" % (activity.props.name, activity.props.id)
|
||||
else:
|
||||
self.ActivityDisappeared(activity.object_path())
|
||||
print "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():
|
||||
loop = gobject.MainLoop()
|
||||
ps = PresenceService()
|
||||
try:
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
ps.cleanup()
|
||||
print 'Ctrl+C pressed, exiting...'
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,518 @@
|
||||
# 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
|
||||
from sugar import profile
|
||||
from sugar import util
|
||||
from sugar import env
|
||||
import gtk
|
||||
from buddyiconcache import BuddyIconCache
|
||||
import logging
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
from telepathy.client import ConnectionManager, ManagerRegistry, Connection, Channel
|
||||
from telepathy.interfaces import (
|
||||
CONN_MGR_INTERFACE, CONN_INTERFACE, CHANNEL_TYPE_CONTACT_LIST, CHANNEL_INTERFACE_GROUP, CONN_INTERFACE_ALIASING,
|
||||
CONN_INTERFACE_AVATARS, CONN_INTERFACE_PRESENCE, CHANNEL_TYPE_TEXT, CHANNEL_TYPE_STREAMED_MEDIA)
|
||||
from telepathy.constants import (
|
||||
CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT,
|
||||
CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, CONNECTION_STATUS_CONNECTING,
|
||||
CONNECTION_HANDLE_TYPE_LIST, CONNECTION_HANDLE_TYPE_CONTACT, CONNECTION_HANDLE_TYPE_ROOM,
|
||||
CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
|
||||
|
||||
CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
|
||||
CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
|
||||
|
||||
_PROTOCOL = "jabber"
|
||||
|
||||
class InvalidBuddyError(Exception):
|
||||
pass
|
||||
|
||||
def _buddy_icon_save_cb(buf, data):
|
||||
data[0] += buf
|
||||
return True
|
||||
|
||||
def _get_buddy_icon_at_size(icon, maxw, maxh, maxsize):
|
||||
loader = gtk.gdk.PixbufLoader()
|
||||
loader.write(icon)
|
||||
loader.close()
|
||||
unscaled_pixbuf = loader.get_pixbuf()
|
||||
del loader
|
||||
|
||||
pixbuf = unscaled_pixbuf.scale_simple(maxw, maxh, gtk.gdk.INTERP_BILINEAR)
|
||||
del unscaled_pixbuf
|
||||
|
||||
data = [""]
|
||||
quality = 90
|
||||
img_size = maxsize + 1
|
||||
while img_size > maxsize:
|
||||
del data
|
||||
data = [""]
|
||||
pixbuf.save_to_callback(_buddy_icon_save_cb, "jpeg", {"quality":"%d" % quality}, data)
|
||||
quality -= 10
|
||||
img_size = len(data[0])
|
||||
del pixbuf
|
||||
|
||||
if img_size > maxsize:
|
||||
del data
|
||||
raise RuntimeError("could not size image less than %d bytes" % maxsize)
|
||||
|
||||
return str(data[0])
|
||||
|
||||
|
||||
class ServerPlugin(gobject.GObject):
|
||||
__gsignals__ = {
|
||||
'contact-online': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
||||
'contact-offline': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_INT, gobject.TYPE_INT])),
|
||||
'avatar-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
||||
'buddy-properties-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
||||
'buddy-activities-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
||||
'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'activity-properties-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
||||
}
|
||||
|
||||
def __init__(self, registry, owner):
|
||||
gobject.GObject.__init__(self)
|
||||
|
||||
self._icon_cache = BuddyIconCache()
|
||||
|
||||
self._gabble_mgr = registry.GetManager('gabble')
|
||||
self._online_contacts = {} # handle -> jid
|
||||
|
||||
self._activities = {} # activity id -> handle
|
||||
self._joined_activities = [] # (activity_id, handle of the activity channel)
|
||||
|
||||
self._owner = owner
|
||||
self._owner.connect("property-changed", self._owner_property_changed_cb)
|
||||
self._owner.connect("icon-changed", self._owner_icon_changed_cb)
|
||||
|
||||
self._account = self._get_account_info()
|
||||
|
||||
self._conn = self._init_connection()
|
||||
|
||||
self._reconnect_id = 0
|
||||
|
||||
def _owner_property_changed_cb(self, owner, properties):
|
||||
print "Owner properties changed: %s" % properties
|
||||
self._set_self_buddy_info()
|
||||
|
||||
def _owner_icon_changed_cb(self, owner, icon):
|
||||
print "Owner icon changed to size %d" % len(str(icon))
|
||||
self._upload_avatar()
|
||||
|
||||
def _get_account_info(self):
|
||||
account_info = {}
|
||||
|
||||
pubkey = self._owner.props.key
|
||||
|
||||
server = profile.get_server()
|
||||
if not server:
|
||||
account_info['server'] = 'olpc.collabora.co.uk'
|
||||
else:
|
||||
account_info['server'] = server
|
||||
|
||||
registered = profile.get_server_registered()
|
||||
account_info['register'] = not registered
|
||||
|
||||
khash = util.printable_hash(util._sha_data(pubkey))
|
||||
account_info['account'] = "%s@%s" % (khash, account_info['server'])
|
||||
|
||||
account_info['password'] = profile.get_private_key_hash()
|
||||
print account_info
|
||||
return account_info
|
||||
|
||||
def _find_existing_connection(self):
|
||||
our_name = self._account['account']
|
||||
|
||||
# Search existing connections, if any, that we might be able to use
|
||||
connections = Connection.get_connections()
|
||||
conn = None
|
||||
for item in connections:
|
||||
if not item.object_path.startswith("/org/freedesktop/Telepathy/Connection/gabble/jabber/"):
|
||||
continue
|
||||
if item[CONN_INTERFACE].GetProtocol() != _PROTOCOL:
|
||||
continue
|
||||
if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED:
|
||||
test_handle = item[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_CONTACT, [our_name])[0]
|
||||
if item[CONN_INTERFACE].GetSelfHandle() != test_handle:
|
||||
continue
|
||||
return item
|
||||
return None
|
||||
|
||||
def get_connection(self):
|
||||
return self._conn
|
||||
|
||||
def _init_connection(self):
|
||||
conn = self._find_existing_connection()
|
||||
if not conn:
|
||||
acct = self._account.copy()
|
||||
|
||||
# Create a new connection
|
||||
name, path = self._gabble_mgr[CONN_MGR_INTERFACE].RequestConnection(_PROTOCOL, acct)
|
||||
conn = Connection(name, path)
|
||||
del acct
|
||||
|
||||
conn[CONN_INTERFACE].connect_to_signal('StatusChanged', self._status_changed_cb)
|
||||
conn[CONN_INTERFACE].connect_to_signal('NewChannel', self._new_channel_cb)
|
||||
|
||||
# hack
|
||||
conn._valid_interfaces.add(CONN_INTERFACE_PRESENCE)
|
||||
conn._valid_interfaces.add(CONN_INTERFACE_BUDDY_INFO)
|
||||
conn._valid_interfaces.add(CONN_INTERFACE_ACTIVITY_PROPERTIES)
|
||||
conn._valid_interfaces.add(CONN_INTERFACE_AVATARS)
|
||||
conn._valid_interfaces.add(CONN_INTERFACE_ALIASING)
|
||||
|
||||
conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate',
|
||||
self._presence_update_cb)
|
||||
|
||||
return conn
|
||||
|
||||
def _request_list_channel(self, name):
|
||||
handle = self._conn[CONN_INTERFACE].RequestHandles(
|
||||
CONNECTION_HANDLE_TYPE_LIST, [name])[0]
|
||||
chan_path = self._conn[CONN_INTERFACE].RequestChannel(
|
||||
CHANNEL_TYPE_CONTACT_LIST, CONNECTION_HANDLE_TYPE_LIST,
|
||||
handle, True)
|
||||
channel = Channel(self._conn._dbus_object._named_service, chan_path)
|
||||
# hack
|
||||
channel._valid_interfaces.add(CHANNEL_INTERFACE_GROUP)
|
||||
return channel
|
||||
|
||||
def _connected_cb(self):
|
||||
if self._account['register']:
|
||||
# we successfully register this account
|
||||
profile.set_server_registered()
|
||||
|
||||
# the group of contacts who may receive your presence
|
||||
publish = self._request_list_channel('publish')
|
||||
publish_handles, local_pending, remote_pending = publish[CHANNEL_INTERFACE_GROUP].GetAllMembers()
|
||||
|
||||
# the group of contacts for whom you wish to receive presence
|
||||
subscribe = self._request_list_channel('subscribe')
|
||||
subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers()
|
||||
|
||||
if local_pending:
|
||||
# accept pending subscriptions
|
||||
#print 'pending: %r' % local_pending
|
||||
publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '')
|
||||
|
||||
not_subscribed = list(set(publish_handles) - set(subscribe_handles))
|
||||
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
|
||||
self._online_contacts[self_handle] = self._account['account']
|
||||
|
||||
for handle in not_subscribed:
|
||||
# request subscriptions from people subscribed to us if we're not subscribed to them
|
||||
subscribe[CHANNEL_INTERFACE_GROUP].AddMembers([self_handle], '')
|
||||
|
||||
if CONN_INTERFACE_BUDDY_INFO not in self._conn.get_valid_interfaces():
|
||||
print 'OLPC information not available'
|
||||
self.cleanup()
|
||||
return
|
||||
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal('PropertiesChanged', self._buddy_properties_changed_cb)
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal('ActivitiesChanged', self._buddy_activities_changed_cb)
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal('CurrentActivityChanged', self._buddy_current_activity_changed_cb)
|
||||
|
||||
self._conn[CONN_INTERFACE_AVATARS].connect_to_signal('AvatarUpdated', self._avatar_updated_cb)
|
||||
|
||||
self._conn[CONN_INTERFACE_ALIASING].connect_to_signal('AliasesChanged', self._alias_changed_cb)
|
||||
|
||||
self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].connect_to_signal('ActivityPropertiesChanged', self._activity_properties_changed_cb)
|
||||
|
||||
try:
|
||||
self._set_self_buddy_info()
|
||||
except RuntimeError, e:
|
||||
print e
|
||||
self.cleanup()
|
||||
return
|
||||
|
||||
# Request presence for everyone on the channel
|
||||
self._conn[CONN_INTERFACE_PRESENCE].GetPresence(subscribe_handles)
|
||||
|
||||
def _upload_avatar(self):
|
||||
icon_data = self._owner.props.icon
|
||||
|
||||
md5 = hashlib.md5()
|
||||
md5.update(icon_data)
|
||||
hash = md5.hexdigest()
|
||||
|
||||
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
|
||||
token = self._conn[CONN_INTERFACE_AVATARS].GetAvatarTokens([self_handle])[0]
|
||||
|
||||
if self._icon_cache.check_avatar(hash, token):
|
||||
# avatar is up to date
|
||||
return
|
||||
|
||||
types, minw, minh, maxw, maxh, maxsize = self._conn[CONN_INTERFACE_AVATARS].GetAvatarRequirements()
|
||||
if not "image/jpeg" in types:
|
||||
print "server does not accept JPEG format avatars."
|
||||
return
|
||||
|
||||
img_data = _get_buddy_icon_at_size(icon_data, min(maxw, 96), min(maxh, 96), maxsize)
|
||||
token = self._conn[CONN_INTERFACE_AVATARS].SetAvatar(img_data, "image/jpeg")
|
||||
self._icon_cache.set_avatar(hash, token)
|
||||
|
||||
def join_activity(self, act):
|
||||
handle = self._activities.get(act)
|
||||
|
||||
if not handle:
|
||||
handle = self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [act])[0]
|
||||
self._activities[act] = handle
|
||||
|
||||
if (act, handle) in self._joined_activities:
|
||||
print "%s already joined" % act
|
||||
return
|
||||
|
||||
chan_path = self._conn[CONN_INTERFACE].RequestChannel(
|
||||
CHANNEL_TYPE_TEXT, CONNECTION_HANDLE_TYPE_ROOM,
|
||||
handle, True)
|
||||
channel = Channel(self._conn._dbus_object._named_service, chan_path)
|
||||
|
||||
self._joined_activities.append((act, handle))
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities(self._joined_activities)
|
||||
|
||||
return channel
|
||||
|
||||
def _set_self_buddy_info(self):
|
||||
# Set our OLPC buddy properties
|
||||
props = {}
|
||||
props['color'] = self._owner.props.color
|
||||
props['key'] = self._owner.props.key
|
||||
try:
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props)
|
||||
except dbus.DBusException, e:
|
||||
if str(e).find("Server does not support PEP") >= 0:
|
||||
raise RuntimeError("Server does not support PEP")
|
||||
|
||||
name = self._owner.props.nick
|
||||
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
|
||||
self._conn[CONN_INTERFACE_ALIASING].SetAliases( {self_handle : name} )
|
||||
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities(self._joined_activities)
|
||||
|
||||
cur_activity = self._owner.props.current_activity
|
||||
cur_activity_handle = 0
|
||||
if not cur_activity:
|
||||
cur_activity = ""
|
||||
else:
|
||||
cur_activity_handle = self._get_handle_for_activity(cur_activity)
|
||||
if not cur_activity_handle:
|
||||
# dont advertise a current activity that's not shared
|
||||
cur_activity = ""
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].SetCurrentActivity(cur_activity, cur_activity_handle)
|
||||
|
||||
self._upload_avatar()
|
||||
|
||||
def _get_handle_for_activity(self, activity_id):
|
||||
for (act, handle) in self._joined_activities:
|
||||
if activity_id == act:
|
||||
return handle
|
||||
return None
|
||||
|
||||
def _status_changed_cb(self, state, reason):
|
||||
if state == CONNECTION_STATUS_CONNECTING:
|
||||
print 'connecting: %r' % reason
|
||||
elif state == CONNECTION_STATUS_CONNECTED:
|
||||
print 'connected: %r' % reason
|
||||
self._connected_cb()
|
||||
self.emit('status', state, int(reason))
|
||||
elif state == CONNECTION_STATUS_DISCONNECTED:
|
||||
print 'disconnected: %r' % reason
|
||||
self.emit('status', state, int(reason))
|
||||
self._conn = None
|
||||
if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
|
||||
# FIXME: handle connection failure; retry later?
|
||||
pass
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
print "Trying to connect..."
|
||||
# If the connection is already connected query initial contacts
|
||||
conn_status = self._conn[CONN_INTERFACE].GetStatus()
|
||||
if conn_status == CONNECTION_STATUS_CONNECTED:
|
||||
self._connected_cb()
|
||||
subscribe = self._request_list_channel('subscribe')
|
||||
subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers()
|
||||
self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles)
|
||||
elif conn_status == CONNECTION_STATUS_CONNECTING:
|
||||
pass
|
||||
else:
|
||||
self._conn[CONN_INTERFACE].Connect(reply_handler=self._connect_reply_cb,
|
||||
error_handler=self._connect_error_cb)
|
||||
|
||||
def _connect_reply_cb(self):
|
||||
if self._reconnect_id > 0:
|
||||
gobject.source_remove(self._reconnect_id)
|
||||
|
||||
def _reconnect(self):
|
||||
self._reconnect_id = 0
|
||||
self.start()
|
||||
return False
|
||||
|
||||
def _connect_error_cb(self, exception):
|
||||
print "Connect error: %s" % exception
|
||||
if not self._reconnect_id:
|
||||
self._reconnect_id = gobject.timeout_add(10000, self._reconnect)
|
||||
|
||||
def cleanup(self):
|
||||
if not self._conn:
|
||||
return
|
||||
self._conn[CONN_INTERFACE].Disconnect()
|
||||
|
||||
def _contact_offline(self, handle):
|
||||
self.emit("contact-offline", handle)
|
||||
del self._online_contacts[handle]
|
||||
|
||||
def _contact_online(self, handle):
|
||||
try:
|
||||
props = self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle)
|
||||
except dbus.DBusException, e:
|
||||
if str(e).startswith("org.freedesktop.DBus.Error.NoReply"):
|
||||
raise InvalidBuddyError("couldn't get properties")
|
||||
props = {}
|
||||
logging.debug("Error getting buddy properties: %s" % e)
|
||||
|
||||
if not props.has_key('color'):
|
||||
raise InvalidBuddyError("no color")
|
||||
if not props.has_key('key'):
|
||||
raise InvalidBuddyError("no key")
|
||||
|
||||
jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0]
|
||||
nick = self._conn[CONN_INTERFACE_ALIASING].RequestAliases([handle])[0]
|
||||
if not nick:
|
||||
raise InvalidBuddyError("no name")
|
||||
props['nick'] = nick
|
||||
|
||||
self._online_contacts[handle] = jid
|
||||
self.emit("contact-online", handle, props)
|
||||
|
||||
activities = self._conn[CONN_INTERFACE_BUDDY_INFO].GetActivities(handle)
|
||||
self._buddy_activities_changed_cb(handle, activities)
|
||||
|
||||
def _presence_update_cb(self, presence):
|
||||
for handle in presence:
|
||||
timestamp, statuses = presence[handle]
|
||||
online = handle in self._online_contacts
|
||||
for status, params in statuses.items():
|
||||
jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0]
|
||||
olstr = "ONLINE"
|
||||
if not online: olstr = "OFFLINE"
|
||||
print "Handle %s (%s) was %s, status now '%s'." % (handle, jid, olstr, status)
|
||||
if not online and status in ["available", "away", "brb", "busy", "dnd", "xa"]:
|
||||
try:
|
||||
self._contact_online(handle)
|
||||
except InvalidBuddyError, e:
|
||||
print "Not adding %s because %s" % (handle, e)
|
||||
elif online and status in ["offline", "invisible"]:
|
||||
self._contact_offline(handle)
|
||||
|
||||
def _avatar_updated_cb(self, handle, new_avatar_token):
|
||||
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
|
||||
# ignore network events for Owner property changes since those
|
||||
# are handled locally
|
||||
return
|
||||
|
||||
jid = self._online_contacts[handle]
|
||||
icon = self._icon_cache.get_icon(jid, new_avatar_token)
|
||||
if not icon:
|
||||
# cache miss
|
||||
avatar, mime_type = self._conn[CONN_INTERFACE_AVATARS].RequestAvatar(handle)
|
||||
icon = ''.join(map(chr, avatar))
|
||||
self._icon_cache.store_icon(jid, new_avatar_token, icon)
|
||||
|
||||
self.emit("avatar-updated", handle, icon)
|
||||
|
||||
def _alias_changed_cb(self, aliases):
|
||||
for handle, alias in aliases:
|
||||
prop = {'nick': alias}
|
||||
#print "Buddy %s alias changed to %s" % (handle, alias)
|
||||
self._buddy_properties_changed_cb(handle, prop)
|
||||
|
||||
def _buddy_properties_changed_cb(self, handle, properties):
|
||||
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
|
||||
# ignore network events for Owner property changes since those
|
||||
# are handled locally
|
||||
return
|
||||
self.emit("buddy-properties-changed", handle, properties)
|
||||
|
||||
def _buddy_activities_changed_cb(self, handle, activities):
|
||||
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
|
||||
# ignore network events for Owner activity changes since those
|
||||
# are handled locally
|
||||
return
|
||||
|
||||
for act_id, act_handle in activities:
|
||||
self._activities[act_id] = act_handle
|
||||
activities_id = map(lambda x: x[0], activities)
|
||||
self.emit("buddy-activities-changed", handle, activities_id)
|
||||
|
||||
def _buddy_current_activity_changed_cb(self, handle, activity, channel):
|
||||
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
|
||||
# ignore network events for Owner current activity changes since those
|
||||
# are handled locally
|
||||
return
|
||||
|
||||
if not len(activity) or not util.validate_activity_id(activity):
|
||||
activity = None
|
||||
prop = {'current-activity': activity}
|
||||
logging.debug("Handle %s: current activity now %s" % (handle, activity))
|
||||
self._buddy_properties_changed_cb(handle, prop)
|
||||
|
||||
def _new_channel_cb(self, object_path, channel_type, handle_type, handle, suppress_handler):
|
||||
if handle_type == CONNECTION_HANDLE_TYPE_ROOM and channel_type == CHANNEL_TYPE_TEXT:
|
||||
channel = Channel(self._conn._dbus_object._named_service, object_path)
|
||||
|
||||
# hack
|
||||
channel._valid_interfaces.add(CHANNEL_INTERFACE_GROUP)
|
||||
|
||||
current, local_pending, remote_pending = channel[CHANNEL_INTERFACE_GROUP].GetAllMembers()
|
||||
|
||||
if local_pending:
|
||||
for act_id, act_handle in self._activities.items():
|
||||
if handle == act_handle:
|
||||
self.emit("activity-invitation", act_id)
|
||||
|
||||
elif handle_type == CONNECTION_HANDLE_TYPE_CONTACT and \
|
||||
channel_type in [CHANNEL_TYPE_TEXT, CHANNEL_TYPE_STREAMED_MEDIA]:
|
||||
self.emit("private-invitation", object_path)
|
||||
|
||||
def set_activity_properties(self, act_id, props):
|
||||
handle = self._activities.get(act_id)
|
||||
|
||||
if not handle:
|
||||
print "set_activity_properties: handle unkown"
|
||||
return
|
||||
|
||||
self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].SetProperties(handle, props)
|
||||
|
||||
def _activity_properties_changed_cb(self, room, properties):
|
||||
for act_id, act_handle in self._activities.items():
|
||||
if room == act_handle:
|
||||
self.emit("activity-properties-changed", act_id, properties)
|
||||
@@ -24,12 +24,12 @@ import os
|
||||
from sugar import logger
|
||||
from sugar import env
|
||||
|
||||
sys.path.insert(0, env.get_service_path('presence'))
|
||||
sys.path.insert(0, env.get_service_path('presence2'))
|
||||
|
||||
logger.start('presenceservice')
|
||||
|
||||
import PresenceService
|
||||
import presenceservice
|
||||
|
||||
logging.info('Starting presence service')
|
||||
|
||||
PresenceService.main()
|
||||
presenceservice.main()
|
||||
|
||||
Reference in New Issue
Block a user