Refcount tracked service types; ensure only the process that registers a service can change it; add support for updated service properties/published values

This commit is contained in:
Dan Williams 2006-09-15 16:41:11 -04:00
parent c36089522b
commit b39eff3365
5 changed files with 270 additions and 68 deletions

View File

@ -1,9 +1,11 @@
import os
import random
import base64
import time
import conf
from sugar import env
import logging
from sugar.p2p import Stream
from sugar.presence import PresenceService
from Friends import Friends
@ -15,7 +17,7 @@ class ShellOwner(object):
"""Class representing the owner of this machine/instance. This class
runs in the shell and serves up the buddy icon and other stuff. It's the
server portion of the Owner, paired with the client portion in Buddy.py."""
def __init__(self):
def __init__(self, shell):
profile = conf.get_profile()
self._nick = profile.get_nick_name()
@ -34,6 +36,12 @@ class ShellOwner(object):
self._friends = Friends()
self._invites = Invites()
self._shell = shell
self._shell.connect('activity-changed', self.__activity_changed_cb)
self._last_activity_update = time.time()
self._pending_activity_update_timer = None
self._pending_activity_update = None
def get_friends(self):
return self._friends
@ -43,10 +51,14 @@ class ShellOwner(object):
def announce(self):
# Create and announce our presence
color = conf.get_profile().get_color()
props = { 'color': color.to_string() }
props = {'color':color.to_string()}
activity = self._shell.get_current_activity()
if activity is not None:
props['cur_activity':activity.get_id()]
self._last_activity_update = time.time()
self._service = self._pservice.register_service(self._nick,
PRESENCE_SERVICE_TYPE, properties=props)
print "Owner '%s' using port %d" % (self._nick, self._service.get_port())
logging.debug("Owner '%s' using port %d" % (self._nick, self._service.get_port()))
self._icon_stream = Stream.Stream.new_from_service(self._service)
self._icon_stream.register_reader_handler(self._handle_buddy_icon_request, "get_buddy_icon")
self._icon_stream.register_reader_handler(self._handle_invite, "invite")
@ -61,3 +73,31 @@ class ShellOwner(object):
"""XMLRPC method, called when the owner is invited to an activity."""
self._invites.add_invite(issuer, bundle_id, activity_id)
return ''
def __update_advertised_current_activity_cb(self):
self._last_activity_update = time.time()
self._pending_activity_update_timer = None
logging.debug("*** Updating current activity to %s" % self._pending_activity_update)
return False
def __activity_changed_cb(self, shell, activity):
"""Update our presence service with the latest activity, but no
more frequently than every 30 seconds"""
self._pending_activity_update = activity.get_id()
# If there's no pending update, we must not have updated it in the
# last 30 seconds (except for the initial update, hence we also check
# for the last update)
if not self._pending_activity_update_timer or time.time() - self._last_activity_update > 30:
self.__update_advertised_current_activity_cb()
return
# If we have a pending update already, we have nothing left to do
if self._pending_activity_update_timer:
return
# Otherwise, we start a timer to update the activity at the next
# interval, which should be 30 seconds from the last update, or if that
# is in the past already, then now
next = 30 - max(30, time.time() - self._last_activity_update)
self._pending_activity_update_timer = gobject.timeout_add(next * 1000,
self.__update_advertised_current_activity_cb)

View File

@ -56,6 +56,38 @@ class ServiceAdv(object):
raise ValueError("Can't reset to resolve pending from resolved.")
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('=')
prop_dict[key] = value
return prop_dict
_PRESENCE_SERVICE = "org.laptop.Presence"
_PRESENCE_DBUS_INTERFACE = "org.laptop.Presence"
@ -169,8 +201,9 @@ class PresenceServiceDBusHelper(dbus.service.Object):
return owner.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="os", out_signature="o")
def joinActivity(self, activity_op, stype):
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:
@ -179,29 +212,34 @@ class PresenceServiceDBusHelper(dbus.service.Object):
break
if not found_activity:
raise NotFoundError("The activity %s was not found." % activity_op)
return self._parent.join_activity(found_activity, stype)
return self._parent.join_activity(found_activity, stype, sender)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="ssa{ss}sis", out_signature="o")
def shareActivity(self, activity_id, stype, properties, address, port, domain):
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)
port, domain, sender)
return service.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="ssa{ss}sis", out_signature="o")
def registerService(self, name, stype, properties, address, port, domain):
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)
port, domain, sender)
return service.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="o", out_signature="")
def unregisterService(self, service_op):
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:
@ -210,7 +248,7 @@ class PresenceServiceDBusHelper(dbus.service.Object):
break
if not found_serv:
raise NotFoundError("The activity %s was not found." % service_op)
return self._parent.unregister_service(found_serv)
return self._parent.unregister_service(found_serv, sender)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="")
@ -424,9 +462,10 @@ class PresenceService(object):
key = (full_name, stype)
if not self._services.has_key(key):
objid = self._get_next_object_id()
props = _txt_to_dict(txt)
service = Service.Service(self._bus_name, objid, name=full_name,
stype=stype, domain=domain, address=address, port=port,
properties=txt, source_address=address, local=adv.is_local())
properties=props, source_address=address, local=adv.is_local())
self._services[key] = service
else:
# Already tracking this service; likely we were the one that shared it
@ -588,19 +627,22 @@ class PresenceService(object):
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):
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()))
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())
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"):
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")
@ -621,14 +663,19 @@ class PresenceService(object):
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)
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"):
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."""
(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!")
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:
@ -647,12 +694,14 @@ class PresenceService(object):
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=True)
properties=properties, source_address=None, local=True,
local_publisher=sender)
self._services[(name, stype)] = service
port = service.get_port()
logging.debug("PS: Will register service with name='%s', stype='%s'," \
" domain='%s', address='%s', port=%d, info='%s'" % (name, stype, domain, address, port, info))
" domain='%s', address='%s', port=%d, info='%s'" % (name, stype,
domain, address, port, info))
group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, dbus.String(name),
dbus.String(stype), dbus.String(domain), dbus.String(""), # let Avahi figure the 'host' out
dbus.UInt16(port), info)
@ -667,7 +716,10 @@ class PresenceService(object):
self.register_service_type(stype)
return service
def unregister_service(self, service):
def unregister_service(self, service, sender=None):
local_publisher = service.get_local_publisher()
if sender is not None and local_publisher != sender:
raise ValueError("Service was not not registered by requesting process!")
group = service.get_avahi_entry_group()
if not group:
raise ValueError("Service was not a local service provided by this laptop!")
@ -678,9 +730,16 @@ class PresenceService(object):
a certain mDNS service types."""
if type(stype) != type(u""):
raise ValueError("service type must be a unicode string.")
if stype in self._registered_service_types:
return
self._registered_service_types.append(stype)
# If we've already registered it as a service type, ref it and return
for item in self._registered_service_types:
if item.get_type() == stype:
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
@ -698,8 +757,15 @@ class PresenceService(object):
"""Stop tracking a certain mDNS service."""
if type(stype) != type(u""):
raise ValueError("service type must be a unicode string.")
if stype in self._registered_service_types:
self._registered_service_types.remove(stype)
item = None
for item in self._registered_service_types:
if item.get_type() == stype:
break
# if it was found, unref it and possibly remove it
if item is not None:
if item.unref() <= 0:
self._registered_service_types.remove(item)
del item
def main():

View File

@ -5,23 +5,6 @@ from sugar import util
import dbus, dbus.service
import random
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('=')
prop_dict[key] = value
return prop_dict
def compose_service_name(name, activity_id):
if type(name) == type(""):
name = unicode(name)
@ -67,6 +50,11 @@ class ServiceDBusHelper(dbus.service.Object):
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):
@ -90,14 +78,33 @@ class ServiceDBusHelper(dbus.service.Object):
return pary
@dbus.service.method(SERVICE_DBUS_INTERFACE,
in_signature="s", out_signature="s")
in_signature="s")
def getPublishedValue(self, key):
"""Return the value belonging to the requested key from the
service's TXT records."""
value = self._parent.get_one_property(key)
if type(value) == type(True):
value = str(value)
return value
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):
self._parent.set_properties(values, sender)
class Service(object):
@ -105,7 +112,7 @@ class Service(object):
service as advertised on the network."""
def __init__(self, bus_name, object_id, name, stype, domain=u"local",
address=None, port=-1, properties=None, source_address=None,
local=False):
local=False, local_publisher=None):
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or type(object_id) != type(1):
@ -137,7 +144,7 @@ class Service(object):
self._port = -1
self.set_port(port)
self._properties = {}
self.set_properties(properties)
self._internal_set_properties(properties, first_time=True)
self._avahi_entry_group = None
self._local = local
@ -159,13 +166,19 @@ class Service(object):
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))
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
# 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
# register ourselves with dbus
self._object_id = object_id
self._object_path = SERVICE_DBUS_OBJECT_PATH + str(self._object_id)
@ -182,6 +195,9 @@ class Service(object):
raise RuntimeError("Can only set a service's owner once")
self._owner = owner
def get_local_publisher(self):
return self._local_publisher
def is_local(self):
return self._local
@ -207,19 +223,55 @@ class Service(object):
properties."""
return self._properties
def set_properties(self, properties):
def set_property(self, key, value, sender=None):
"""Set one service property"""
if sender is not None and self._local_publisher != sender:
raise ValueError("Service was not not registered by requesting process!")
if type(key) != type(u""):
raise ValueError("Key must be a unicode string.")
if type(value) != type(u"") or type(value) != type(True):
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 type(value) == type(u"") and len(value) == 0:
remove = True
if type(value) == type(False) 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 type(value) == type(True):
value = ""
self._properties[key] = value
self._dbus_helper.PublishedValueChanged(key)
def set_properties(self, properties, sender=None):
"""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)
def _internal_set_properties(self, properties, first_time=False):
"""Set the service's properties from either an Avahi
TXT record (a list of lists of integers), or a
python dictionary."""
if type(properties) != type({}):
raise ValueError("Properties must be a dictionary.")
self._properties = {}
props = {}
if type(properties) == type([]):
props = _txt_to_dict(properties)
elif type(properties) == type({}):
props = properties
# Set key/value pairs on internal property list
for key, value in props.items():
for key, value in properties.items():
if len(key) == 0:
continue
tmp_key = key
@ -230,6 +282,9 @@ class Service(object):
tmp_val = unicode(tmp_val)
self._properties[tmp_key] = tmp_val
if not first_time:
self._dbus_helper.PublishedValueChanged(self._properties.keys())
def get_type(self):
"""Return the service's service type."""
return self._stype

View File

@ -73,7 +73,7 @@ class Shell(gobject.GObject):
PresenceService.start()
self._pservice = PresenceService.get_instance()
self._owner = ShellOwner()
self._owner = ShellOwner(self)
self._owner.announce()
self._home_window.set_owner(self._owner)

View File

@ -2,11 +2,29 @@ import gobject
import dbus
def __one_dict_differs(dict1, dict2):
for key, value in dict1.items():
if not dict2.has_key(key) or dict2[key] != value:
return True
return False
def __dicts_differ(dict1, dict2):
if __one_dict_differs(dict1, dict2):
return True
if __one_dict_differs(dict2, dict1):
return True
return False
class Service(gobject.GObject):
_PRESENCE_SERVICE = "org.laptop.Presence"
_SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service"
__gsignals__ = {
'published-value-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
gobject.GObject.__init__(self)
self._object_path = object_path
@ -14,17 +32,40 @@ class Service(gobject.GObject):
self._ps_del_object = del_obj_cb
sobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
self._service = dbus.Interface(sobj, self._SERVICE_DBUS_INTERFACE)
self._service.connect_to_signal('PropertyChanged', self._property_changed_cb)
self._service.connect_to_signal('PropertyChanged', self.__property_changed_cb)
self._service.connect_to_signal('PublishedValueChanged',
self.__published_value_changed_cb)
self._props = self._service.getProperties()
self._pubvals = self._service.getPublishedValues()
def object_path(self):
return self._object_path
def _property_changed_cb(self, prop_list):
def __property_changed_cb(self, prop_list):
self._props = self._service.getProperties()
def get_published_value(self, key):
return self._service.getPublishedValue(key)
return self._pubvals[key]
def get_published_values(self):
self._pubvals = self._service.getPublishedValues()
def set_published_value(self, key, value):
if self._pubvals.has_key(key):
if self._pubvals[key] == value:
return
self._pubvals[key] = value
self._service.setPublishedValue(key, value)
def set_published_values(self, vals):
self._service.setPublishedValues(vals)
self._pubvals = vals
def __published_value_changed_cb(self, keys):
oldvals = self._pubvals
self.get_published_values()
if __dicts_differ(oldvals, self._pubvals):
self.emit('published-value-changed', keys)
def get_name(self):
return self._props['name']