More PS bits

This commit is contained in:
Dan Williams 2006-07-22 01:26:39 -04:00
parent 2aa23cfa42
commit ea264a1a83
4 changed files with 328 additions and 54 deletions

View File

@ -7,6 +7,7 @@ import dbus, dbus.service
PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp"
BUDDY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Buddies/"
BUDDY_DBUS_INTERFACE = "org.laptop.Presence.Buddy"
class NotFoundError(Exception):
@ -85,6 +86,8 @@ class Buddy(object):
raise ValueError("DBus bus name must be valid")
if not object_id or type(object_id) != type(1):
raise ValueError("object id must be a valid number")
if not isinstance(service, Service.Service):
raise ValueError("service must be a valid service object")
self._services = {}
self._activities = {}
@ -97,7 +100,7 @@ class Buddy(object):
self._owner = owner
self._object_id = object_id
self._object_path = "/org/laptop/Presence/Buddies/%d" % self._object_id
self._object_path = BUDDY_DBUS_OBJECT_PATH + str(self._object_id)
self._dbus_helper = BuddyDBusHelper(self, bus_name, self._object_path)
self.add_service(service)
@ -255,3 +258,73 @@ class Owner(Buddy):
portion of the Owner, paired with the server portion in Owner.py."""
def __init__(self, bus_name, object_id, service):
Buddy.__init__(self, bus_name, object_id, service, owner=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_nick_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()

View File

@ -68,6 +68,9 @@ _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
@ -104,6 +107,74 @@ class PresenceServiceDBusHelper(dbus.service.Object):
def ActivityDisappeared(self, object_path):
pass
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getServices(self):
services = self._parent.get_services()
ret = []
for serv in services:
ret.append(serv.object_path())
return ret
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="ao")
def getServicesOfType(self, stype):
services = self._parent.get_services_of_type(stype)
ret = []
for serv in services:
ret.append(serv.object_path())
return ret
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getActivities(self):
activities = self._parent.get_activities()
ret = []
for act in 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):
buddies = self._parent.get_buddies()
ret = []
for buddy in 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()
class PresenceService(object):
def __init__(self):
@ -154,6 +225,41 @@ class PresenceService(object):
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):
return self._activities.values()
def get_activity(self, actid):
if self._activities.has_key(actid):
return self._activities[actid]
return None
def get_buddies(self):
return self._buddies.values()
def get_buddy_by_name(self, name):
if self._buddies.has_key(name):
return self._buddies[name]
return None
def get_buddy_by_address(self, address):
for buddy in self._buddies.values():
if buddy.get_address() == address:
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):
"""Search a list of service advertisements for ones matching certain criteria."""
adv_list = []
@ -247,11 +353,16 @@ class PresenceService(object):
if adv in self._resolve_queue:
self._resolve_queue.remove(adv)
# Update the service now that it's been resolved
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=txt)
# See if we know about this service already
key = (full_name, stype)
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=txt)
self._services[key] = service
else:
service = self._services[key]
adv.set_service(service)
# Merge the service into our buddy and activity lists, if needed
@ -347,7 +458,8 @@ class PresenceService(object):
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):
@ -403,6 +515,84 @@ 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 register_service(self, name, stype, properties, address, port, domain):
"""Register a new service, advertising it to other Buddies on the network."""
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)
self._services[key] = service
if self.get_owner() and name != self.get_owner().get_nick_name():
raise RuntimeError("Tried to register a service that didn't have Owner nick as the service name!")
actid = service.get_activity_id()
if actid:
rs_name = Service.compose_service_name(rs_name, actid)
rs_stype = service.get_type()
rs_port = service.get_port()
rs_props = service.get_properties()
rs_domain = service.get_domain()
rs_address = service.get_address()
if not rs_domain or not len(rs_domain):
rs_domain = ""
logging.debug("registered service name '%s' type '%s' on port %d with args %s" % (rs_name, rs_stype, rs_port, rs_props))
try:
group = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, self._server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP)
# Add properties; ensure they are converted to ByteArray types
# because python sometimes can't figure that out
info = []
for k, v in rs_props.items():
tmp_item = "%s=%s" % (k, v)
info.append(dbus.types.ByteArray(tmp_item))
if rs_address and len(rs_address):
info.append("address=%s" % (rs_address))
logging.debug("PS: about to call AddService for Avahi with rs_name='%s' (%s), rs_stype='%s' (%s)," \
" rs_domain='%s' (%s), rs_port=%d (%s), info='%s' (%s)" % (rs_name, type(rs_name), rs_stype,
type(rs_stype), rs_domain, type(rs_domain), rs_port, type(rs_port), info, type(info)))
group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, rs_name, rs_stype,
rs_domain, "", # let Avahi figure the 'host' out
dbus.UInt16(rs_port), info,)
group.Commit()
except dbus.dbus_bindings.DBusException, exc:
# FIXME: ignore local name collisions, since that means
# the zeroconf service is already registered. Ideally we
# should un-register it an re-register with the correct info
if str(exc) == "Local name collision":
pass
activity_stype = service.get_type()
self.register_service_type(activity_stype)
def register_service_type(self, stype):
"""Requests that the Presence service look for and recognize
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)
# 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 not in self._resolve_queue:
self._resolve_queue.append(adv)
gobject.idle_add(self._resolve_service, adv)
def unregister_service_type(self, stype):
"""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)
def main():

View File

@ -22,6 +22,8 @@ def _txt_to_dict(txt):
return prop_dict
def compose_service_name(name, activity_id):
if type(name) == type(""):
name = unicode(name)
if not name:
raise ValueError("name must be a valid string.")
if not activity_id:
@ -29,7 +31,7 @@ def compose_service_name(name, activity_id):
if type(name) != type(u""):
raise ValueError("name must be in unicode.")
composed = "%s [%s]" % (name, activity_id)
return composed.encode()
return composed
def _decompose_service_name(name):
"""Break a service name into the name and activity ID, if we can."""
@ -58,7 +60,7 @@ def is_multicast_address(address):
return False
if address[3] != '.':
return False
first = int(address[:3])
first = int(float(address[:3]))
if first >= 224 and first <= 239:
return True
return False
@ -66,6 +68,7 @@ def is_multicast_address(address):
_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"""
@ -105,10 +108,11 @@ class ServiceDBusHelper(dbus.service.Object):
value = str(value)
return value
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, address=None, port=-1, properties=None):
def __init__(self, bus_name, object_id, name, stype, domain=u"local", address=None, port=-1, properties=None):
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or type(object_id) != type(1):
@ -127,13 +131,14 @@ class Service(object):
if not stype.endswith("._tcp") and not stype.endswith("._udp"):
raise ValueError("must specify a TCP or UDP service type.")
if domain and type(domain) != type(u""):
if type(domain) != type(u""):
raise ValueError("domain must be in unicode.")
if len(domain) and domain != "local":
if domain and domain != "local":
raise ValueError("must use the 'local' domain (for now).")
(actid, real_name) = _decompose_service_name(name)
self._name = real_name
self._full_name = name
self._stype = stype
self._domain = domain
self._port = -1
@ -163,7 +168,7 @@ class Service(object):
# register ourselves with dbus
self._object_id = object_id
self._object_path = "/org/laptop/Presence/Services/%d" % self._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):
@ -182,6 +187,9 @@ class Service(object):
buddy who provides it."""
return self._name
def get_full_name(self):
return self._full_name
def is_multicast_service(self):
"""Return True if the service's address is a multicast address,
False if it is not."""
@ -261,6 +269,13 @@ class Service(object):
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"
@ -268,13 +283,23 @@ class ServiceTestCase(unittest.TestCase):
_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:
service = Service(name, stype, domain, address, port, properties)
objid = _next_objid()
service = Service(self._bus_name, objid, name, stype, domain, address,
port, properties)
except ValueError, exc:
pass
else:
@ -289,7 +314,7 @@ class ServiceTestCase(unittest.TestCase):
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, "_bork._foobar", self._DEF_DOMAIN, self._DEF_ADDRESS,
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):
@ -297,14 +322,12 @@ class ServiceTestCase(unittest.TestCase):
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, "foobar", self._DEF_ADDRESS,
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
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName('org.laptop.Presence', bus=session_bus)
service = Service(bus_name, 1, self._DEF_NAME, self._DEF_STYPE, "", self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS)
del bus_name, session_bus
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):
@ -324,16 +347,15 @@ class ServiceTestCase(unittest.TestCase):
"adf", self._DEF_PROPS, "invalid port")
def testGoodInit(self):
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName('org.laptop.Presence', bus=session_bus)
service = Service(bus_name, 1, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS)
del bus_name, session_bus
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."
@ -341,11 +363,9 @@ class ServiceTestCase(unittest.TestCase):
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"
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName('org.laptop.Presence', bus=session_bus)
service = Service(bus_name, 1, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, props)
del bus_name, session_bus
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')
@ -355,32 +375,29 @@ class ServiceTestCase(unittest.TestCase):
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
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName('org.laptop.Presence', bus=session_bus)
service = Service(bus_name, 1, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
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)
del bus_name, session_bus
assert value is not None and value == expected_value, "service properties weren't correct after init."
def testGroupService(self):
def testActivityService(self):
# Valid group service type, non-multicast address
group_stype = u"_af5e5a7c998e89b9a_group_olpc._udp"
self._test_init_fail(self._DEF_NAME, group_stype, self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS, "group service type, but non-multicast address")
actid = "4569a71b80805aa96a847f7ac1c407327b3ec2b4"
name = compose_service_name("Tommy", actid)
# Valid group service type, None address
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName('org.laptop.Presence', bus=session_bus)
service = Service(bus_name, 1, self._DEF_NAME, group_stype, self._DEF_DOMAIN, None,
# 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 group service type and multicast address, ensure it works
# Valid activity service name and multicast address, ensure it works
mc_addr = u"224.0.0.34"
service = Service(bus_name, 1, self._DEF_NAME, group_stype, self._DEF_DOMAIN, mc_addr,
objid = _next_objid()
service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, mc_addr,
self._DEF_PORT, self._DEF_PROPS)
del bus_name, session_bus
assert service.get_address() == mc_addr, "address was not expected address!"
def addToSuite(suite):
@ -392,7 +409,7 @@ class ServiceTestCase(unittest.TestCase):
suite.addTest(ServiceTestCase("testGoodInit"))
suite.addTest(ServiceTestCase("testAvahiProperties"))
suite.addTest(ServiceTestCase("testBoolProperty"))
suite.addTest(ServiceTestCase("testGroupService"))
suite.addTest(ServiceTestCase("testActivityService"))
addToSuite = staticmethod(addToSuite)

View File

@ -10,8 +10,6 @@ from sugar.presence import Service
class Stream(object):
def __init__(self, service):
if not isinstance(service, Service.Service):
raise ValueError("service must be valid.")
if not service.get_port():
raise ValueError("service must have an address.")
self._service = service
@ -21,8 +19,6 @@ class Stream(object):
self._callback = None
def new_from_service(service, start_reader=True):
if not isinstance(service, Service.Service):
raise ValueError("service must be valid.")
if service.is_multicast_service():
return MulticastStream(service)
else:
@ -40,8 +36,6 @@ class Stream(object):
class UnicastStreamWriter(object):
def __init__(self, stream, service):
# set up the writer
if not isinstance(service, Service.Service):
raise ValueError("service must be valid")
self._service = service
if not service.get_address():
raise ValueError("service must have a valid address.")