Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2006-06-09 21:23:42 (GMT)
committer Dan Williams <dcbw@redhat.com>2006-06-09 21:23:42 (GMT)
commitd931dca5799ee1e88ce327bf28424f9739f4ad87 (patch)
tree5ffd9e4588616c4ceb88cf24dd385c4e1921a408
parentc4b112366ce91ddedbb3a7a1c6caa7543b0256c8 (diff)
Main bits of presence service refactor
-rw-r--r--sugar/presence/Buddy.py127
-rw-r--r--sugar/presence/PresenceService.py373
-rw-r--r--sugar/presence/Service.py208
3 files changed, 708 insertions, 0 deletions
diff --git a/sugar/presence/Buddy.py b/sugar/presence/Buddy.py
new file mode 100644
index 0000000..5201a04
--- /dev/null
+++ b/sugar/presence/Buddy.py
@@ -0,0 +1,127 @@
+import pwd
+import os
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+
+
+#from sugar import env
+
+PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp"
+
+
+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, service):
+ self._services = {}
+ self._nick_name = service.get_name()
+ self._address = service.get_address()
+ self._valid = False
+ self._icon = None
+ self.add_service(service)
+
+ def add_service(self, service):
+ """Adds a new service to this buddy's service list."""
+ if service.get_type() in self._services.keys():
+ return
+ self._services.keys[service.get_type()] = service
+ # FIXME: send out signal for new service found
+ if service.get_type() == PRESENCE_SERVICE_TYPE:
+ # A buddy isn't valid until its official presence
+ # service has been found and resolved
+ self._valid = True
+
+ def remove_service(self, service):
+ """Remove a service from a buddy; ie, the activity was closed
+ or the buddy went away."""
+ if service.get_type() in self._services.keys():
+ del self._services[service.get_type()]
+ if service.get_type() == PRESENCE_SERVICE_TYPE:
+ self._valid = False
+
+ 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_pixbuf(self):
+ if self._icon:
+ pbl = gtk.gdk.PixbufLoader()
+ pbl.write(self._icon)
+ pbl.close()
+ return pbl.get_pixbuf()
+ else:
+ return None
+
+ def get_icon(self):
+ """Return the buddies icon, if any."""
+ return self._icon
+
+ def get_address(self):
+ return self._address
+
+ def add_service(self, service):
+ if service.get_name() != self._nick_name:
+ return False
+ if service.get_address() != self._address:
+ return False
+ if self._services.has_key(service.get_type()):
+ return False
+ self._services[service.get_type()] = service
+
+ def remove_service(self, stype):
+ if self._services.has_key(stype):
+ del self._services[stype]
+
+ def get_service(self, stype):
+ if self._services.has_key(stype):
+ return self._services[stype]
+ return None
+
+ def get_nick_name(self):
+ return self._nick_name
+
+ def set_icon(self, icon):
+ """Can only set icon for other buddies. The Owner
+ takes care of setting it's own icon."""
+ self._icon = icon
+ # FIXME: do callbacks for icon-changed
+
+
+class Owner(Buddy):
+ """Class representing the owner of this machine/instance."""
+ def __init__(self):
+ nick = env.get_nick_name()
+ if not nick:
+ nick = pwd.getpwuid(os.getuid())[0]
+ if not nick or not len(nick):
+ nick = "n00b"
+
+ Buddy.__init__(self)
+
+ user_dir = env.get_user_dir()
+ if not os.path.exists(user_dir):
+ try:
+ os.makedirs(user_dir)
+ except OSError:
+ print 'Could not create user directory.'
+
+ for fname in os.listdir(user_dir):
+ if not fname.startswith("buddy-icon."):
+ continue
+ fd = open(os.path.join(user_dir, fname), "r")
+ self._icon = fd.read()
+ fd.close()
+ break
+
+ def set_icon(self, icon):
+ """Can only set icon in constructor for now."""
+ pass
+
+ def add_service(self, service):
+ """Do nothing here, since all services we need to know about
+ are registered with us by our group."""
+ pass
diff --git a/sugar/presence/PresenceService.py b/sugar/presence/PresenceService.py
new file mode 100644
index 0000000..4dbbf0e
--- /dev/null
+++ b/sugar/presence/PresenceService.py
@@ -0,0 +1,373 @@
+import threading
+import avahi, dbus, dbus.glib, dbus.dbus_bindings, gobject
+import Buddy
+import Service
+import os
+
+
+ACTION_SERVICE_APPEARED = 'appeared'
+ACTION_SERVICE_DISAPPEARED = 'disappeared'
+
+class PresenceService(object):
+ """Object providing information about the presence of Buddies
+ and what activities they make available to others."""
+
+ __lock = threading.Lock()
+ __instance = None
+
+ def get_instance():
+ """Return, creating if needed, the singleton PresenceService
+ object."""
+ PresenceService.__lock.acquire()
+ if not PresenceService.__instance:
+ PresenceService.__instance = PresenceService()
+ PresenceService.__lock.release()
+ return PresenceService.__instance
+ get_instance = staticmethod(get_instance)
+
+ def __init__(self, debug=False):
+ self._debug = debug
+ self._lock = threading.Lock()
+ self._started = False
+
+ # nick -> Buddy: buddies we've found
+ self._buddies = {}
+ # group UID -> Group: groups we've found
+ self._groups = {}
+
+ # All the mdns service types we care about
+ self._allowed_service_types = []
+
+ # Keep track of stuff we're already browsing with ZC
+ self._service_type_browsers = {}
+ self._service_browsers = {}
+
+ # We only resolve services that our clients are interested in;
+ # but we store unresolved services so that when a client does
+ # become interested in a new service type, we can quickly
+ # resolve it
+ self._unresolved_services = []
+
+ self._bus = dbus.SystemBus()
+ self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME,
+ avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+
+ def start(self):
+ """Start the presence service by kicking off service discovery."""
+ self._lock.acquire()
+ if self._started:
+ self._lock.release()
+ return
+ self._started = True
+ self._lock.release()
+
+ # Always browse .local
+ self._new_domain_cb(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local")
+
+ # Connect to Avahi and start looking for stuff
+ domain_browser = self._server.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))
+ db = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, domain_browser), avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
+ db.connect_to_signal('ItemNew', self._new_domain_cb_glue)
+
+ def set_debug(self, debug):
+ self._debug = debug
+
+ def _log(self, msg):
+ """Simple logger."""
+ if self._debug:
+ print "PresenceService(%d): %s" % (os.getpid(), msg)
+
+ def _resolve_service_error_handler(self, err):
+ self._log("error resolving service: %s" % err)
+
+ def _find_service(self, slist, name=None, stype=None, domain=None, address=None, port=None):
+ """Search a list of services for ones matching certain criteria."""
+ found = []
+ for service in slist:
+ if name and service.get_name() != name:
+ continue
+ if stype and service.get_type() != stype:
+ continue
+ if domain and service.get_domain() != domain:
+ continue
+ if address and service.get_address() != address:
+ continue
+ if port and service.get_port() != port:
+ continue
+ found.append(service)
+ return found
+
+ def _resolve_service_reply_cb(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+ """When the service discovery finally gets here, we've got enough information about the
+ service to assign it to a buddy."""
+ self._log("resolved service '%s' type '%s' domain '%s' to %s:%s" % (name, stype, domain, address, port))
+
+ # If this service was previously unresolved, remove it from the
+ # unresolved list
+ found = self._find_service(self._unresolved_services, name=name,
+ stype=stype, domain=domain)
+ if not len(found):
+ return False
+
+ for service in found:
+ self._unresolved_services.remove(service)
+
+ # Update the service now that it's been resolved
+ service = found[0]
+ service.set_address(address)
+ service.set_port(port)
+ service.set_properties(txt)
+
+ # 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
+ added = was_valid = False
+ try:
+ buddy = self._buddies[name]
+ was_valid = buddy.is_valid()
+ added = buddy.add_service(service)
+ except KeyError:
+ buddy = Buddy.Buddy(service)
+ self._buddies[name] = buddy
+ added = True
+ if not was_valid and buddy.is_valid():
+ # FIXME: send out "new buddy" signals
+ pass
+ if added:
+ # FIXME: send out buddy service added signals
+ pass
+ return False
+
+ def _resolve_service_reply_cb_glue(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+ gobject.idle_add(self._resolve_service_reply_cb, interface, protocol,
+ name, stype, domain, host, aprotocol, address, port, txt, flags)
+
+ def _resolve_service(self, interface, protocol, name, stype, domain, flags):
+ """Resolve and lookup a ZeroConf service to obtain its address and TXT records."""
+ # Ask avahi to resolve this particular service
+ self._server.ResolveService(int(interface), int(protocol), name,
+ stype, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), # use flags here maybe?
+ reply_handler=self._resolve_service_reply_cb_glue,
+ error_handler=self._resolve_service_error_handler)
+ return False
+
+ def _service_appeared_cb(self, interface, protocol, name, stype, domain, flags):
+ self._log("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (name, flags, stype, domain, interface, protocol))
+
+ # Add the service to our unresolved services list
+ found = self._find_service(self._unresolved_services, name=name,
+ stype=stype, domain=domain)
+ if not len(found):
+ service = Service.Service(name, stype, domain)
+ self._unresolved_services.append(service)
+
+ # If we care about the service right now, resolve it
+ if stype in self._allowed_service_types or stype == Buddy.PRESENCE_SERVICE_TYPE:
+ gobject.idle_add(self._resolve_service, interface, protocol, name, stype, domain, flags)
+ 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, name, stype, domain, flags):
+ self._log("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, stype, domain, interface, protocol))
+ # Remove the service from our unresolved services list
+ found = self._find_service(self._unresolved_services, name=name,
+ stype=stype, domain=domain)
+
+ buddy = None
+ try:
+ buddy = self._buddies[name]
+ except KeyError:
+ pass
+
+ # Remove the service from the buddy
+ if buddy:
+ buddy.remove_service(found[0])
+ # FIXME: send buddy service remove signals
+ if not buddy.is_valid():
+ del self._buddies[name]
+ # FIXME: send buddy disappeared message
+
+ for service in found:
+ self._unresolved_services.remove(service)
+ 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
+ s_browser = self._server.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0))
+ browser_obj = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, s_browser), avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+ self._log("now browsing for services of type '%s' in domain '%s' on %i.%i ..." % (stype, domain, interface, protocol))
+ 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
+ return False
+
+ def _new_service_type_cb_glue(self, interface, protocol, stype, domain, flags):
+ 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._server.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0))
+ browser_obj = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, st_browser), avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER)
+ except dbus.DBusException, exc:
+ self._log("got exception %s while attempting to browse domain %s on %i.%i" % (domain, interface, protocol))
+ str_exc = str(exc)
+ 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
+ self._log("now 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 track_service_type(self, stype):
+ """Requests that the Presence service look for and recognize
+ a certain mDNS service types."""
+ if not type(stype) == type(""):
+ raise ValueError("service type must be a string.")
+ if stype == Buddy.PRESENCE_SERVICE_TYPE:
+ return
+ if stype in self._allowed_service_types:
+ return
+
+ self._allowed_service_types.append(stype)
+
+ # Find unresolved services that match the service type
+ # we're now interested in, and resolve them
+ found = self._find_service(self._unresolved_services, stype=stype)
+ for service in found:
+ gobject.idle_add(self._resolve_service, interface, protocol, name, stype, domain, flags)
+
+ def untrack_service_type(self, stype):
+ """Stop tracking a certain mDNS service."""
+ if not type(stype) == type(""):
+ raise ValueError("service type must be a string.")
+ if name in self._allowed_service_types:
+ self._allowed_service_types.remove(stype)
+
+ def register_service(self, service):
+ """Register a new service, advertising it to other Buddies on the network."""
+ rs_name = service.get_name()
+ rs_stype = service.get_type()
+ rs_port = service.get_port()
+ if type(rs_port) != type(1) and rs_port <= 1024:
+ raise ValueError("invalid service port.")
+ rs_props = service.get_properties()
+ self._log("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)
+ info = ["%s=%s" % (k, v) for k, v in rs_props.items()]
+ group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, rs_name, rs_stype,
+ "", "", # domain, host (let the system figure it 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
+ self.track_service_type(rs_stype)
+ return group
+
+ def get_buddy_by_nick_name(self, nick_name):
+ if self._buddies.has_key(nick_name):
+ return self._buddies[nick_name]
+ return None
+
+ def get_buddies(self):
+ return self._buddies.values()
+
+#################################################################
+# Tests
+#################################################################
+
+import unittest
+
+ps = None
+
+class PresenceServiceTestCase(unittest.TestCase):
+ _DEF_NAME = "Paul"
+ _DEF_STYPE = Buddy.PRESENCE_SERVICE_TYPE
+ _DEF_DOMAIN = "local"
+ _DEF_PORT = 3333
+ _DEF_PROPERTIES = {"foo": "bar", "bork": "baz"}
+
+ def testNoServices(self):
+ """Ensure that no services are found initially."""
+ """This test may illegitimately fail if there's another person
+ on the network running sugar... So its usefulness is somewhat
+ dubious."""
+ import gtk
+ global ps
+ buddies = ps.get_buddies()
+ assert len(buddies) == 0, "A buddy was found without setting tracked services!"
+ gtk.main_quit()
+
+ def testServiceRegistration(self):
+ service = Service.Service(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
+ address=None, port=self._DEF_PORT, properties=self._DEF_PROPERTIES)
+ global ps
+ ps.register_service(service)
+ # Give the Presence Service some time to find the new service
+ gobject.timeout_add(2000, self.quitMain)
+ import gtk
+ gtk.main()
+
+ def quitMain(self):
+ import gtk
+ gtk.main_quit()
+
+ def testServiceDetection(self):
+ global ps
+ buddy = ps.get_buddy_by_nick_name("Paul")
+ assert buddy, "The registered buddy was not found after 2 seconds!"
+ assert buddy.is_valid(), "The buddy was invalid, since no presence was advertised."
+
+ def addToSuite(suite):
+ suite.addTest(PresenceServiceTestCase("testNoServices"))
+ suite.addTest(PresenceServiceTestCase("testServiceRegistration"))
+ suite.addTest(PresenceServiceTestCase("testServiceDetection"))
+ addToSuite = staticmethod(addToSuite)
+
+def runTests():
+ suite = unittest.TestSuite()
+ PresenceServiceTestCase.addToSuite(suite)
+ runner = unittest.TextTestRunner()
+ runner.run(suite)
+
+def main():
+ import pygtk, gtk
+ global ps
+ ps = PresenceService.get_instance()
+ ps.set_debug(True)
+ ps.start()
+ gobject.timeout_add(4000, runTests)
+ gtk.main()
+
+if __name__ == "__main__":
+ main()
diff --git a/sugar/presence/Service.py b/sugar/presence/Service.py
new file mode 100644
index 0000000..c709dd8
--- /dev/null
+++ b/sugar/presence/Service.py
@@ -0,0 +1,208 @@
+import avahi
+
+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
+
+class Service(object):
+ """Encapsulates information about a specific ZeroConf/mDNS
+ service as advertised on the network."""
+ def __init__(self, name, stype, domain, address=None, port=-1, properties=None):
+ # Validate immutable options
+ if not name or (type(name) != type("") and type(name) != type(u"")) or not len(name):
+ raise ValueError("must specify a valid service name.")
+
+ if not stype or (type(stype) != type("") and type(stype) != type(u"")) or not len(stype):
+ raise ValueError("must specify a service type.")
+ if not stype.endswith("._tcp") and not stype.endswith("._udp"):
+ raise ValueError("must specify a TCP or UDP service type.")
+
+ if not domain or (type(domain) != type("") and type(domain) != type(u"")):
+ raise ValueError("must specify a domain.")
+ if domain != "local" and domain != u"local":
+ raise ValueError("must use the 'local' domain (for now).")
+
+ self._name = name
+ self._stype = stype
+ self._domain = domain
+ self._address = None
+ self.set_address(address)
+ self._port = -1
+ self.set_port(port)
+ self._properties = {}
+ self.set_properties(properties)
+
+ def get_name(self):
+ return self._name
+
+ def get_one_property(self, key):
+ if key in self._properties.keys():
+ return self._properties[key]
+ return None
+
+ def get_properties(self):
+ return self._properties
+
+ def set_properties(self, properties):
+ self._properties = {}
+ if type(properties) == type([]):
+ self._properties = _txt_to_dict(properties)
+ elif type(properties) == type({}):
+ self._properties = properties
+
+ def get_type(self):
+ return self._stype
+
+ def get_port(self):
+ return self._port
+
+ def set_port(self, port):
+ if type(port) != type(1):
+ raise ValueError("must specify a valid port number.")
+ self._port = port
+
+ def get_address(self):
+ return self._address
+
+ def set_address(self, address):
+ if address is not None:
+ if type(address) != type("") and type(address) != type(u""):
+ raise ValueError("must specify a valid address.")
+ if not len(address):
+ raise ValueError("must specify a valid address.")
+ self._address = address
+
+ def get_domain(self):
+ return self._domain
+
+ def is_olpc_service(self):
+ if self._stype.endswith("._olpc._udp") or self._stype.endswith(".olpc._tcp"):
+ return True
+ return False
+
+
+#################################################################
+# Tests
+#################################################################
+
+import unittest
+
+class ServiceTestCase(unittest.TestCase):
+ _DEF_NAME = "foobar"
+ _DEF_STYPE = "_foo._bar._tcp"
+ _DEF_DOMAIN = "local"
+ _DEF_ADDRESS = "1.1.1.1"
+ _DEF_PORT = 1234
+ _DEF_PROPS = {'foobar': 'baz'}
+
+ _STR_TEST_ARGS = [None, 0, [], {}]
+
+ 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)
+ 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, "_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, "foobar", self._DEF_ADDRESS,
+ self._DEF_PORT, self._DEF_PROPS, "invalid domain")
+
+ 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):
+ service = Service(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."
+ 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"
+ service = Service(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
+ service = Service(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 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"))
+ addToSuite = staticmethod(addToSuite)
+
+
+def main():
+ suite = unittest.TestSuite()
+ ServiceTestCase.addToSuite(suite)
+ runner = unittest.TextTestRunner()
+ runner.run(suite)
+
+if __name__ == "__main__":
+ main()