Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/services/presence/Service.py
diff options
context:
space:
mode:
Diffstat (limited to 'services/presence/Service.py')
-rw-r--r--services/presence/Service.py133
1 files changed, 109 insertions, 24 deletions
diff --git a/services/presence/Service.py b/services/presence/Service.py
index 0c23600..1b29f98 100644
--- a/services/presence/Service.py
+++ b/services/presence/Service.py
@@ -4,6 +4,7 @@ sys.path.insert(0, os.path.abspath("../../"))
from sugar import util
import dbus, dbus.service
import random
+import logging
def compose_service_name(name, activity_id):
if type(name) == type(""):
@@ -37,6 +38,31 @@ def decompose_service_name(name):
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"
@@ -104,15 +130,18 @@ class ServiceDBusHelper(dbus.service.Object):
@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(object):
"""Encapsulates information about a specific ZeroConf/mDNS
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_publisher=None):
+ 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):
@@ -136,6 +165,12 @@ class Service(object):
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
@@ -143,10 +178,9 @@ class Service(object):
self._domain = domain
self._port = -1
self.set_port(port)
- self._properties = {}
- self._internal_set_properties(properties, first_time=True)
- self._avahi_entry_group = None
- self._local = local
+ self._properties = None
+ self._dbus_helper = None
+ self._internal_set_properties(properties)
# Source address is the unicast source IP
self._source_address = None
@@ -175,10 +209,6 @@ class Service(object):
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)
@@ -195,11 +225,10 @@ 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
+ if self._local_publisher is not None:
+ return True
+ return False
def get_name(self):
"""Return the service's name, usually that of the
@@ -225,12 +254,14 @@ class Service(object):
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 type(key) != type(u""):
raise ValueError("Key must be a unicode string.")
- if type(value) != type(u"") or type(value) != type(True):
+ if type(value) != type(u"") and type(value) != type(True):
raise ValueError("Key must be a unicode string or a boolean.")
# Ignore setting the key to it's current value
@@ -254,15 +285,22 @@ class Service(object):
if type(value) == type(True):
value = ""
self._properties[key] = value
- self._dbus_helper.PublishedValueChanged(key)
- def set_properties(self, properties, sender=None):
+ # 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()
+
+ if self._dbus_helper:
+ self._dbus_helper.PublishedValueChanged([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)
- def _internal_set_properties(self, properties, first_time=False):
+ 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."""
@@ -270,6 +308,11 @@ class Service(object):
raise ValueError("Properties must be a dictionary.")
self._properties = {}
+ # Make sure the properties are actually different
+ diff_keys = _dicts_differ(self._properties, properties)
+ if len(diff_keys) == 0:
+ return
+
# Set key/value pairs on internal property list
for key, value in properties.items():
if len(key) == 0:
@@ -282,8 +325,19 @@ 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())
+ # 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()
+
+ if self._dbus_helper:
+ self._dbus_helper.PublishedValueChanged(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."""
@@ -322,11 +376,42 @@ class Service(object):
"""Return the ZeroConf/mDNS domain the service was found in."""
return self._domain
- def set_avahi_entry_group(self, group):
- self._avahi_entry_group = group
+ 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):
+ 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
- def get_avahi_entry_group(self):
- return self._avahi_entry_group
#################################################################
# Tests