From 766b82d46706c0a9f4e32635e27db8aa55a1b820 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 19 Jul 2006 15:27:37 -0400 Subject: [PATCH] Switch sugar/presence module over to a thin API wrapper around the PresenceService dbus API --- shell/Owner.py | 3 - sugar/presence/Activity.py | 99 ++++ sugar/presence/Buddy.py | 229 +++------ sugar/presence/Makefile.am | 1 + sugar/presence/PresenceService.py | 746 ++++++------------------------ sugar/presence/Service.py | 405 +--------------- 6 files changed, 327 insertions(+), 1156 deletions(-) create mode 100644 sugar/presence/Activity.py diff --git a/shell/Owner.py b/shell/Owner.py index bd8771d3..90d7d31e 100644 --- a/shell/Owner.py +++ b/shell/Owner.py @@ -3,9 +3,6 @@ import random import base64 from sugar import env -from sugar.presence import Service -from sugar.presence import Buddy -from sugar.presence import PresenceService from sugar.p2p import Stream class ShellOwner(object): diff --git a/sugar/presence/Activity.py b/sugar/presence/Activity.py new file mode 100644 index 00000000..24281268 --- /dev/null +++ b/sugar/presence/Activity.py @@ -0,0 +1,99 @@ +import gobject +import dbus, dbus_bindings + +class Activity(gobject.GObject): + + __gsignals__ = { + 'BuddyJoined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'BuddyLeft': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'ServiceAppeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'ServiceDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + _PRESENCE_SERVICE = "org.laptop.Presence" + _ACTIVITY_DBUS_INTERFACE = "org.laptop.Presence.Activity" + + def __init__(self, bus, new_obj_cb, del_obj_cb, object_path): + gobject.GObject.__init__(self) + self._object_path = object_path + self._ps_new_object = new_obj_cb + self._ps_del_object = del_obj_cb + bobj = bus.get_object(self._PRESENCE_SERVICE, object_path) + self._activity = dbus.Interface(bobj, self._ACTIVITY_DBUS_INTERFACE) + self._activity.connect_to_signal('BuddyJoined', self._buddy_joined_cb) + self._activity.connect_to_signal('BuddyLeft', self._buddy_left_cb) + self._activity.connect_to_signal('ServiceAppeared', self._service_appeared_cb) + self._activity.connect_to_signal('ServiceDisappeared', self._service_disappeared_cb) + + def object_path(self): + return self._object_path + + def _emit_buddy_joined_signal(self, object_path): + self.emit('BuddyJoined', self._ps_new_object(object_path)) + return False + + def _buddy_joined_cb(self, object_path): + gobject.idle_add(self._emit_buddy_joined_signal, object_path) + + def _emit_buddy_left_signal(self, object_path): + self.emit('BuddyLeft', self._ps_new_object(object_path)) + return False + + def _buddy_left_cb(self, object_path): + gobject.idle_add(self._emit_buddy_left_signal, object_path) + + def _emit_service_appeared_signal(self, object_path): + self.emit('ServiceAppeared', self._ps_new_object(object_path)) + return False + + def _service_appeared_cb(self, object_path): + gobject.idle_add(self._emit_service_appeared_signal, object_path) + + def _emit_service_disappeared_signal(self, object_path): + self.emit('ServiceDisappeared', self._ps_new_object(object_path)) + return False + + def _service_disappeared_cb(self, object_path): + gobject.idle_add(self._emit_service_disappeared_signal, object_path) + + def getId(self): + return self._activity.getId() + + def getIcon(self): + return self._buddy.getIcon() + + def getServiceOfType(self, stype): + try: + object_path = self._buddy.getServiceOfType(stype) + except dbus_bindings.DBusException: + return None + return self._ps_new_object(object_path) + + def getServices(self): + resp = self._activity.getServices() + servs = [] + for item in resp: + servs.append(self._ps_new_object(item)) + return servs + + def getServicesOfType(self, stype): + resp = self._activity.getServicesOfType(stype) + servs = [] + for item in resp: + servs.append(self._ps_new_object(item)) + return servs + + def getJoinedBuddies(self): + resp = self._activity.getJoinedBuddies(stype) + buddies = [] + for item in resp: + buddies.append(self._ps_new_object(item)) + return buddies + + def ownerHasJoined(self): + # FIXME + return False diff --git a/sugar/presence/Buddy.py b/sugar/presence/Buddy.py index b91cb7da..ca269460 100644 --- a/sugar/presence/Buddy.py +++ b/sugar/presence/Buddy.py @@ -1,189 +1,94 @@ -import base64 -import logging - -import gtk import gobject - -from sugar.p2p import Stream -from sugar.p2p import network -from sugar.presence import Service - -PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp" +import dbus, dbus_bindings class Buddy(gobject.GObject): - """Represents another person on the network and keeps track of the - activities and resources they make available for sharing.""" + __gsignals__ = { - 'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + 'IconChanged': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), - 'service-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + 'ServiceAppeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'service-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + 'ServiceDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_STRING])), - 'left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_STRING])) + 'JoinedActivity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'LeftActivity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) } - def __init__(self, service): + _PRESENCE_SERVICE = "org.laptop.Presence" + _BUDDY_DBUS_INTERFACE = "org.laptop.Presence.Buddy" + + def __init__(self, bus, new_obj_cb, del_obj_cb, object_path): gobject.GObject.__init__(self) - self._services = {} - self._nick_name = service.get_name() - self._address = service.get_publisher_address() - self._valid = False - self._icon = None - self._icon_tries = 0 - self._owner = False - self.add_service(service) + self._object_path = object_path + self._ps_new_object = new_obj_cb + self._ps_del_object = del_obj_cb + bobj = bus.get_object(self._PRESENCE_SERVICE, object_path) + self._buddy = dbus.Interface(bobj, self._BUDDY_DBUS_INTERFACE) + self._buddy.connect_to_signal('IconChanged', self._icon_changed_cb) + self._buddy.connect_to_signal('ServiceAppeared', self._service_appeared_cb) + self._buddy.connect_to_signal('ServiceDisappeared', self._service_disappeared_cb) + self._buddy.connect_to_signal('JoinedActivity', self._joined_activity_cb) + self._buddy.connect_to_signal('LeftActivity', self._left_activity_cb) - def _request_buddy_icon_cb(self, result_status, response, user_data): - """Callback when icon request has completed.""" - icon = response - service = user_data - if result_status == network.RESULT_SUCCESS: - if icon and len(icon): - icon = base64.b64decode(icon) - print "Buddy icon for '%s' is size %d" % (self._nick_name, len(icon)) - self.set_icon(icon) + def object_path(self): + return self._object_path - if (result_status == network.RESULT_FAILED or not icon) and self._icon_tries < 3: - self._icon_tries = self._icon_tries + 1 - print "Failed to retrieve buddy icon for '%s' on try %d of %d" % (self._nick_name, \ - self._icon_tries, 3) - gobject.timeout_add(1000, self._request_buddy_icon, service) + def _emit_icon_changed_signal(self): + self.emit('IconChanged') return False - def _request_buddy_icon(self, service): - """Contact the buddy to retrieve the buddy icon.""" - 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._request_buddy_icon, service) + def _icon_changed_cb(self): + gobject.idle_add(self._emit_icon_changed_signal) + + def _emit_service_appeared_signal(self, object_path): + self.emit('ServiceAppeared', self._ps_new_object(object_path)) return False - 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: - return False - publisher_addr = service.get_publisher_address() - if publisher_addr != self._address: - logging.error('Service publisher and buddy address doesnt match: %s %s' % (publisher_addr, self._address)) - return False - stype = service.get_type() - if stype in self._services.keys(): - return False - self._services[stype] = service - if self._valid: - self.emit("service-added", service) + def _service_appeared_cb(self, object_path): + gobject.idle_add(self._emit_service_appeared_signal, object_path) - # If this is the first service we've seen that's owned by - # a particular activity, send out the 'joined-activity' signal - actid = service.get_activity_id() - if actid is not None: - found = False - for serv in self._services.values(): - if serv.get_activity_id() == actid and serv.get_type() != stype: - found = True - break - if not found: - print "Buddy (%s) joined activity %s." % (self._nick_name, actid) - self.emit("joined-activity", service) + def _emit_service_disappeared_signal(self, object_path): + self.emit('ServiceDisappeared', self._ps_new_object(object_path)) + return False - if stype == PRESENCE_SERVICE_TYPE: - # A buddy isn't valid until its official presence - # service has been found and resolved - self._valid = True - print 'Requesting buddy icon %s' % self._nick_name - self._request_buddy_icon(service) - return True + def _service_disappeared_cb(self, object_path): + gobject.idle_add(self._emit_service_disappeared_signal, 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_publisher_address() != self._address: - return - if service.get_name() != self._nick_name: - return - stype = service.get_type() - if self._services.has_key(stype): - if self._valid: - self.emit("service-removed", service) - del self._services[stype] + def _emit_joined_activity_signal(self, object_path): + self.emit('JoinedActivity', self._ps_new_object(object_path)) + return False - # If this is the lase service owned by a particular activity, - # and it's just been removed, send out the 'left-actvity' signal - actid = service.get_activity_id() - if actid is not None: - found = False - for serv in self._services.values(): - if serv.get_activity_id() == actid: - found = True - break - if not found: - print "Buddy (%s) left activity %s." % (self._nick_name, actid) - self.emit("left-activity", service) + def _joined_activity_cb(self, object_path): + gobject.idle_add(self._emit_joined_activity_signal, object_path) - if stype == PRESENCE_SERVICE_TYPE: - self._valid = False + def _emit_left_activity_signal(self, object_path): + self.emit('LeftActivity', self._ps_new_object(object_path)) + return False - def get_service_of_type(self, stype=None, 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.") + def _left_activity_cb(self, object_path): + gobject.idle_add(self._emit_left_activity_signal, object_path) - if activity: - actid = activity.get_id() - for service in self._services.values(): - if service.get_type() == stype and service.get_activity_id() == actid: - return service - if self._services.has_key(stype): - return self._services[stype] - return None + def getProperties(self): + return self._buddy.getProperties() - 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 getIcon(self): + return self._buddy.getIcon() - def get_icon_pixbuf(self): - if self._icon: - pbl = gtk.gdk.PixbufLoader() - pbl.write(self._icon) - pbl.close() - return pbl.get_pixbuf() - else: + def getServiceOfType(self, stype): + try: + object_path = self._buddy.getServiceOfType(stype) + except dbus_bindings.DBusException: return None + return self._ps_new_object(object_path) - def get_icon(self): - """Return the buddies icon, if any.""" - return self._icon - - def get_address(self): - return self._address - - 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.""" - if icon != self._icon: - self._icon = icon - self.emit("icon-changed") - - def is_owner(self): - return self._owner - - -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, service): - Buddy.__init__(self, service) - self._owner = True + def getJoinedActivities(self): + try: + resp = self._buddy.getJoinedActivities() + except dbus_bindings.DBusException: + return [] + acts = [] + for item in resp: + acts.append(self._ps_new_object(item)) + return acts diff --git a/sugar/presence/Makefile.am b/sugar/presence/Makefile.am index dc556bab..234fc21b 100644 --- a/sugar/presence/Makefile.am +++ b/sugar/presence/Makefile.am @@ -1,6 +1,7 @@ sugardir = $(pythondir)/sugar/presence sugar_PYTHON = \ __init__.py \ + Activity.py \ Buddy.py \ PresenceService.py \ Service.py diff --git a/sugar/presence/PresenceService.py b/sugar/presence/PresenceService.py index f38fb085..ac00d1fa 100644 --- a/sugar/presence/PresenceService.py +++ b/sugar/presence/PresenceService.py @@ -1,638 +1,176 @@ -import threading -import avahi, dbus, dbus.glib, dbus.dbus_bindings, gobject -import Buddy -import Service -import random -import logging -from sugar import env +import dbus, dbus.glib, dbus.dbus_bindings, gobject -def _get_local_ip_address(ifname): - """Call Linux specific bits to retrieve our own IP address.""" - import socket - import sys - import fcntl +import Buddy, Service, Activity - addr = None - SIOCGIFADDR = 0x8915 - sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - ifreq = (ifname + '\0'*32)[:32] - result = fcntl.ioctl(sockfd.fileno(), SIOCGIFADDR, ifreq) - addr = socket.inet_ntoa(result[20:24]) - except IOError, exc: - print "Error getting IP address: %s" % exc - sockfd.close() - return addr +class ObjectCache(object): + def __init__(self): + self._cache = {} + def get(self, object_path): + try: + return self._cache[object_path] + except KeyError: + return None -class ServiceAdv(object): - """Wrapper class for service attributes that Avahi passes back.""" - def __init__(self, interface, protocol, name, stype, domain): - self._interface = interface - self._protocol = protocol - self._name = name - self._stype = stype - self._domain = domain - self._service = None - self._resolved = False - - 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 service(self): - return self._service - def set_service(self, service): - if not isinstance(service, Service.Service): - raise ValueError("must be a valid service.") - self._service = service - def resolved(self): - return self._resolved - def set_resolved(self, resolved): - self._resolved = resolved + def add(self, obj): + op = obj.object_path() + if not self._cache.has_key(op): + self._cache[op] = obj + def remove(self, object_path): + if self._cache.has_key(object_path): + del self._cache[object_path] class PresenceService(gobject.GObject): - """Object providing information about the presence of Buddies - and what activities they make available to others.""" __gsignals__ = { - 'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + 'BuddyAppeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + 'BuddyDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'service-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), - 'service-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), - 'activity-announced': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), - 'new-service-adv': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_STRING, gobject.TYPE_STRING])) + 'ServiceAppeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'ServiceDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'ActivityAppeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'ActivityDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) } - __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) + _PRESENCE_SERVICE = "org.laptop.Presence" + _PRESENCE_DBUS_INTERFACE = "org.laptop.Presence" + _PRESENCE_OBJECT_PATH = "/org/laptop/Presence" + _PS_BUDDY_OP = _PRESENCE_OBJECT_PATH + "/Buddies/" + _PS_SERVICE_OP = _PRESENCE_OBJECT_PATH + "/Services/" + _PS_ACTIVITY_OP = _PRESENCE_OBJECT_PATH + "/Activities/" + def __init__(self): gobject.GObject.__init__(self) + self._obcache = ObjectCache() + self._bus = dbus.SessionBus() + self._ps = dbus.Interface(self._bus.get_object(self._PRESENCE_SERVICE, + self._PRESENCE_OBJECT_PATH), self._PRESENCE_DBUS_INTERFACE) + self._ps.connect_to_signal('BuddyAppeared', self._buddy_appeared_cb) + self._ps.connect_to_signal('BuddyDisappeared', self._buddy_disappeared_cb) - self._lock = threading.Lock() - self._started = False + def _new_object(self, object_path): + obj = self._objcache.get(object_path) + if not obj: + if object_path.startswith(self._PS_SERVICE_OP): + obj = Service.Service(self._bus, self._new_object, + self._del_object, object_path) + elif object_path.startswith(self._PS_BUDDY_OP): + obj = Buddy.Buddy(self._bus, self._new_object, + self._del_object, object_path) + elif object_path.startswith(self._PS_ACTIVITY_OP): + obj = Activity.Activity(self._bus, self._new_object, + self._del_object, object_path) + else: + raise RuntimeError("Unknown object type") + self._objcache.add(obj) + return obj - # interface -> IP address: interfaces we've gotten events on so far - self._local_addrs = {} + def _del_object(self, object_path): + # FIXME + pass - # nick -> Buddy: buddies we've found - self._buddies = {} - # Our owner object - self._owner = None + def _emit_buddy_appeared_signal(self, object_path): + self.emit('BuddyAppeared', self._new_object(object_path)) + return False - # activity ID -> Service: services grouped by activity ID - self._activity_services = {} + def _buddy_appeared_cb(self, op): + gobject.idle_add(self._emit_buddy_appeared_signal, op) - # All the mdns service types we care about - self._allowed_service_types = [] # Short service type + def _emit_buddy_disappeared_signal(self, object_path): + self.emit('BuddyDisappeared', self._ps_new_object(object_path)) + return False - # Keep track of stuff we're already browsing with ZC - self._service_type_browsers = {} - self._service_browsers = {} - self._resolve_queue = [] # Track resolve requests + def _buddy_disappeared_cb(self, object_path): + gobject.idle_add(self._emit_buddy_disappeared_signal, object_path) - # Resolved service list - self._service_advs = [] + def _emit_service_appeared_signal(self, object_path): + self.emit('ServiceAppeared', self._ps_new_object(object_path)) + return False - self._bus = dbus.SystemBus() - self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, - avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) + def _service_appeared_cb(self, object_path): + gobject.idle_add(self._emit_service_appeared_signal, object_path) - def get_service(self, stype): - """Find a particular service by full service type.""" - services = self._find_service_adv(stype=stype) - if len(services) > 0: - return services[0] - else: + def _emit_service_disappeared_signal(self, object_path): + self.emit('ServiceDisappeared', self._ps_new_object(object_path)) + return False + + def _service_disappeared_cb(self, object_path): + gobject.idle_add(self._emit_service_disappeared_signal, object_path) + + def _emit_activity_appeared_signal(self, object_path): + self.emit('ActivityAppeared', self._ps_new_object(object_path)) + return False + + def _activity_appeared_cb(self, object_path): + gobject.idle_add(self._emit_activity_appeared_signal, object_path) + + def _emit_activity_disappeared_signal(self, object_path): + self.emit('ActivityDisappeared', self._ps_new_object(object_path)) + return False + + def _activity_disappeared_cb(self, object_path): + gobject.idle_add(self._emit_activity_disappeared_signal, object_path) + + def getServices(self): + resp = self._ps.getServices() + servs = [] + for item in resp: + servs.append(self._new_object(item)) + return servs + + def getServicesOfType(self, stype): + resp = self._ps.getServicesOfType(stype) + servs = [] + for item in resp: + servs.append(self._new_object(item)) + return servs + + def getActivities(self): + resp = self._ps.getActivities() + acts = [] + for item in resp: + acts.append(self._new_object(item)) + return acts + + def getActivity(self, activity_id): + try: + act_op = self._ps.getActivity(activity_id) + except dbus_bindings.DBusException: return None + return self._new_object(act_op) - def get_activity_service(self, activity, stype): - """Find a particular service by activity and service type.""" - actid = activity.get_id() - if self._activity_services.has_key(actid): - services = self._activity_services[actid] - for (buddy, service) in services: - if service.get_type() == stype: - return service - return None + def getBuddies(self): + resp = self._ps.getBuddies() + buddies = [] + for item in resp: + buddies.append(self._new_object(item)) + return buddies - 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 get_owner(self): - """Return the owner of this machine/instance, if we've recognized them yet.""" - return self._owner - - def _resolve_service_error_handler(self, err): - logging.error("error resolving service: %s" % err) - - 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 = [] - 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 - adv_list.append(adv) - return adv_list - - def _is_special_service_type(self, stype): - """Return True if the service type is a special, internal service - type, and False if it's not.""" - if stype == Buddy.PRESENCE_SERVICE_TYPE: - return True - return False - - def _handle_new_service_for_buddy(self, service): - """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 + def getBuddyByName(self, name): try: - buddy = self._buddies[name] - buddy_was_valid = buddy.is_valid() - service_added = buddy.add_service(service) - if service_added: - self.emit('service-appeared', buddy, service) - except KeyError: - # Should this service mark the owner? - owner_nick = env.get_nick_name() - publisher_addr = service.get_publisher_address() - if name == owner_nick and publisher_addr in self._local_addrs.values(): - buddy = Buddy.Owner(service) - self._owner = buddy - logging.debug("Owner is '%s'." % name) - else: - buddy = Buddy.Buddy(service) - self._buddies[name] = buddy - self.emit('service-appeared', buddy, service) - if not buddy_was_valid and buddy.is_valid(): - self.emit("buddy-appeared", buddy) - return buddy + buddy_op = self._ps.getBuddyByName(name) + except dbus_bindings.DBusException: + return None + return self._new_object(buddy_op) - def _handle_new_service_for_activity(self, service, buddy): - # If the serivce is a group service, merge it into our groups list - actid = service.get_activity_id() - if not actid: - actid = "*" - if not self._activity_services.has_key(actid): - self._activity_services[actid] = [] - self._activity_services[actid].append((buddy, service)) - self.emit('activity-announced', service, buddy) - - def _handle_remove_service_for_activity(self, service, buddy): - actid = service.get_activity_id() - if not actid: - actid = "*" - if self._activity_services.has_key(actid): - try: - self._activity_services.remove((buddy, service)) - except: - pass - - def _resolve_service_reply_cb(self, interface, protocol, full_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.""" - logging.debug("resolved service '%s' type '%s' domain '%s' to %s:%s" % (full_name, stype, domain, address, port)) - - full_name = full_name.encode() - stype = stype.encode() - domain = domain.encode() - host = host.encode() - address = address.encode() - - # If this service was previously unresolved, remove it from the - # unresolved list - adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=full_name, stype=stype, domain=domain) - if not adv_list: - return False - adv = adv_list[0] - adv.set_resolved(True) - if adv in self._resolve_queue: - self._resolve_queue.remove(adv) - - # Update the service now that it's been resolved - service = Service.Service(name=full_name, stype=stype, domain=domain, - address=address, port=port, properties=txt) - adv.set_service(service) - - # Merge the service into our buddy and group lists, if needed - buddy = self._handle_new_service_for_buddy(service) - if buddy and service.get_activity_id(): - self._handle_new_service_for_activity(service, buddy) - - 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, adv): - """Resolve and lookup a ZeroConf service to obtain its address and TXT records.""" - # Ask avahi to resolve this particular service - logging.debug('resolving service %s %s' % (adv.name(), adv.stype())) - self._server.ResolveService(int(adv.interface()), int(adv.protocol()), adv.name(), - adv.stype(), adv.domain(), avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=self._resolve_service_reply_cb_glue, - error_handler=self._resolve_service_error_handler) - return False - - def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags): - logging.debug("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (full_name, flags, stype, domain, interface, protocol)) - - # Add the service to our unresolved services list - adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=full_name.encode(), stype=stype.encode(), domain=domain.encode()) - adv = None - if not adv_list: - adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name.encode(), - stype=stype.encode(), domain=domain.encode()) - self._service_advs.append(adv) - else: - adv = adv_list[0] - - # Find out the IP address of this interface, if we haven't already - if interface not in self._local_addrs.keys(): - ifname = self._server.GetNetworkInterfaceNameByIndex(interface) - if ifname: - addr = _get_local_ip_address(ifname) - if addr: - self._local_addrs[interface] = addr - - # Decompose service type if we can - (actid, buddy_name) = Service._decompose_service_name(full_name.encode()) - - # FIXME: find a better way of letting the StartPage get everything - self.emit('new-service-adv', actid, stype) - - # If we care about the service right now, resolve it - resolve = False - if actid is not None or stype in self._allowed_service_types: - resolve = True - if self._is_special_service_type(stype): - resolve = True - if resolve and not adv in self._resolve_queue: - self._resolve_queue.append(adv) - gobject.idle_add(self._resolve_service, adv) - else: - logging.debug("Do not resolve service '%s' of type '%s', we don't care about it." % (full_name, stype)) - - 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): - logging.debug("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (full_name, stype, domain, interface, protocol)) - full_name = full_name.encode() - stype = stype.encode() - domain = domain.encode() - - # 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) - if not adv_list: - return False - - # Get the service object; if none, we have nothing left to do - adv = adv_list[0] - if adv in self._resolve_queue: - self._resolve_queue.remove(adv) - service = adv.service() - if not service: - return False - - # Decompose service type if we can - (actid, buddy_name) = Service._decompose_service_name(full_name) - - # Remove the service from the buddy + def getBuddyByAddress(self, addr): try: - buddy = self._buddies[buddy_name] - except KeyError: - pass - else: - buddy.remove_service(service) - self.emit('service-disappeared', buddy, service) - if not buddy.is_valid(): - self.emit("buddy-disappeared", buddy) - del self._buddies[buddy_name] - self._handle_remove_service_for_activity(service, buddy) + buddy_op = self._ps.getBuddyByAddress(addr) + except dbus_bindings.DBusException: + return None + return self._new_object(buddy_op) - 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) - logging.debug("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 + def getOwner(self): 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: - logging.error("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 - logging.debug("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 + owner_op = self._ps.getOwner() + except dbus_bindings.DBusException: + return None + return self._new_object(buddy_op) - 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 self._started: - raise RuntimeError("presence service must be started first.") - if type(stype) == type(u""): - raise ValueError("service type should not be unicode.") - if type(stype) != type(""): - raise ValueError("service type must be a string.") - if self._is_special_service_type(stype): - return - if stype in self._allowed_service_types: - return - - # Decompose service type if we can - self._allowed_service_types.append(stype) - self._check_and_resolve_service_advs(stype) - - def _check_and_resolve_service_advs(self, 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 untrack_service_type(self, stype): - """Stop tracking a certain mDNS service.""" - if not self._started: - raise RuntimeError("presence service must be started first.") - if type(stype) == type(u""): - raise ValueError("service type should not be unicode.") - if not type(stype) == type(""): - raise ValueError("service type must be a string.") - - if stype in self._allowed_service_types: - self._allowed_service_types.remove(stype) - - def join_shared_activity(self, service): - """Convenience function to join a group and notify other buddies - that you are a member of it.""" - if not isinstance(service, Service.Service): - raise ValueError("service was not a valid service object.") - self.register_service(service) - - def share_activity(self, activity, stype, properties=None, address=None, port=None): - """Convenience function to share an activity with other buddies.""" - if not self._started: - raise RuntimeError("presence service must be started first.") - actid = activity.get_id() - owner_nick = self._owner.get_nick_name() - real_name = Service.compose_service_name(owner_nick, actid) - if address and type(address) != type(""): - raise ValueError("address must be a valid string.") - if address == None: - # Use random currently unassigned multicast address - address = "232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254), - random.randint(1, 254)) - if port and (type(port) != type(1) or port <= 1024 or port >= 65535): - raise ValueError("port must be a number between 1024 and 65535") - if not port: - # random port # - port = random.randint(5000, 65535) - - # Mark the activity as shared - if stype == activity.get_default_type(): - activity.set_shared() - - logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (actid, stype, address, port, properties)) - service = Service.Service(name=real_name, stype=stype, domain="local", - address=address, port=port, properties=properties) - # Publish it to the world - self.register_service(service) - return service - - def register_service(self, service): - """Register a new service, advertising it to other Buddies on the network.""" - if not self._started: - raise RuntimeError("presence service must be started first.") - - rs_name = service.get_name() - if self.get_owner() and rs_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) - # Convert to local encoding for consistency (for now) - if type(tmp_item) == type(u""): - tmp_item = tmp_item.encode() - 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.track_service_type(activity_stype) - return group - - def get_buddy_by_nick_name(self, nick_name): - """Look up and return a buddy by nickname.""" - if self._buddies.has_key(nick_name): - return self._buddies[nick_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_buddies(self): - """Return the entire buddy list.""" - 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." - assert buddy.is_owner() == True, "The buddy was not the owner, but it should be!" - - 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 gtk - global ps - ps = PresenceService.get_instance() - 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 index c82d0191..f5e6eaec 100644 --- a/sugar/presence/Service.py +++ b/sugar/presence/Service.py @@ -1,394 +1,25 @@ -import avahi -from sugar import util -import dbus - -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 not activity_id: - return name - if type(name) == type(u""): - raise ValueError("name must not be in unicode.") - if not name or type(name) != type(""): - raise ValueError("name must be a valid string.") - composed = "%s [%s]" % (name, activity_id) - return composed.encode() - -def _decompose_service_name(name): - """Break a service name into the name and activity ID, if we can.""" - if type(name) != type(""): - raise ValueError("name must be a valid 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 is_multicast_address(address): - """Simple numerical check for whether an IP4 address - is in the range for multicast addresses or not.""" - if not address: - return False - if address[3] != '.': - return False - first = int(address[:3]) - if first >= 224 and first <= 239: - return True - return False - -def deserialize(sdict): - try: - name = sdict['name'] - if type(name) == type(u""): - name = name.encode() - stype = sdict['stype'] - if type(stype) == type(u""): - stype = stype.encode() - domain = sdict['domain'] - if type(domain) == type(u""): - domain = domain.encode() - port = sdict['port'] - properties = sdict['properties'] - except KeyError, exc: - raise ValueError("Serialized service object was not valid.") - - address = None - try: - address = sdict['address'] - if type(address) == type(u""): - address = address.encode() - except KeyError: - pass - - activity_id = None - try: - activity_id = sdict['activity_id'] - if type(activity_id) == type(u""): - activity_id = activity_id.encode() - except KeyError: - pass - - if activity_id is not None: - name = compose_service_name(name, activity_id) - - return Service(name, stype, domain, address=address, - port=port, properties=properties) +import gobject +import dbus, dbus_bindings -_ACTIVITY_ID_TAG = "ActivityID" +class Service(gobject.GObject): -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 name and type(name) == type(u""): - raise ValueError("name must not be in unicode.") - if not name or type(name) != type("") or not len(name): - raise ValueError("must specify a valid service name.") + _PRESENCE_SERVICE = "org.laptop.Presence" + _SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service" - if stype and type(stype) == type(u""): - raise ValueError("service type must not be in unicode.") - if not stype or type(stype) != type("") 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.") + def __init__(self, bus, new_obj_cb, del_obj_cb, object_path): + gobject.GObject.__init__(self) + self._object_path = object_path + self._ps_new_object = new_obj_cb + 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) - if domain and type(domain) == type(u""): - raise ValueError("domain must not be in unicode.") - if type(domain) != type(""): - raise ValueError("must specify a domain.") - if len(domain) and domain != "local": - raise ValueError("must use the 'local' domain (for now).") + def object_path(self): + return self._object_path - (actid, real_name) = _decompose_service_name(name) - self._name = real_name - self._stype = stype - self._domain = domain - self._port = -1 - self.set_port(port) - self._properties = {} - self.set_properties(properties) - # Publisher address is the unicast source IP - self._publisher_address = 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']) - else: - self.set_address(address) + def getProperties(self): + return self._service.getProperties() - # 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 - - def serialize(self, owner=None): - sdict = {} - if owner is not None: - sdict['name'] = dbus.Variant(owner.get_nick_name()) - else: - sdict['name'] = dbus.Variant(self._name) - sdict['stype'] = dbus.Variant(self._stype) - if self._activity_id: - sdict['activity_id'] = dbus.Variant(self._activity_id) - sdict['domain'] = dbus.Variant(self._domain) - if self._address: - sdict['address'] = dbus.Variant(self._address) - sdict['port'] = dbus.Variant(self._port) - sdict['properties'] = dbus.Variant(self._properties) - return sdict - - def get_name(self): - """Return the service's name, usually that of the - buddy who provides it.""" - return self._name - - def is_multicast_service(self): - """Return True if the service's address is a multicast address, - False if it is not.""" - return is_multicast_address(self._address) - - 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 set_properties(self, properties): - """Set the service's properties from either an Avahi - TXT record (a list of lists of integers), or a - python 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, - # also convert everything to local encoding (for now) - # to ensure consistency - for key, value in props.items(): - tmp_key = key - tmp_val = value - if type(tmp_key) == type(u""): - tmp_key = tmp_key.encode() - if type(tmp_val) == type(u""): - tmp_val = tmp_val.encode() - self._properties[tmp_key] = tmp_val - - 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 type(port) != type(1) or (port <= 1024 and port > 65536): - raise ValueError("must specify a valid port number between 1024 and 65536.") - self._port = port - - def get_publisher_address(self): - return self._publisher_address - - 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 address and type(address) == type(u""): - address = address.encode() - self._address = address - - def get_domain(self): - """Return the ZeroConf/mDNS domain the service was found in.""" - return self._domain - - -################################################################# -# 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") - # Make sure "" works - service = Service(self._DEF_NAME, self._DEF_STYPE, "", 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): - 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 testGroupService(self): - # Valid group service type, non-multicast address - group_stype = "_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") - - # Valid group service type, None address - service = Service(self._DEF_NAME, group_stype, self._DEF_DOMAIN, None, - self._DEF_PORT, self._DEF_PROPS) - assert service.get_address() == None, "address was not None as expected!" - # Set address to invalid multicast address - try: - service.set_address(self._DEF_ADDRESS) - except ValueError, exc: - pass - else: - self.fail("expected a ValueError for invalid address.") - - # Valid group service type and multicast address, ensure it works - mc_addr = "224.0.0.34" - service = Service(self._DEF_NAME, group_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("testGroupService")) - addToSuite = staticmethod(addToSuite) - - -def main(): - suite = unittest.TestSuite() - ServiceTestCase.addToSuite(suite) - runner = unittest.TextTestRunner() - runner.run(suite) - -if __name__ == "__main__": - main() + def getPublishedValue(self, key): + value = self._service.getPublishedValue(key)