Switch sugar/presence module over to a thin API wrapper around the PresenceService dbus API

master
Dan Williams 18 years ago
parent d2fdd64003
commit 766b82d467

@ -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):

@ -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

@ -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])),
'ServiceDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'service-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
'JoinedActivity': (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]))
'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)
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)
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)
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 object_path(self):
return self._object_path
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)
# 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)
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 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]
# 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)
if stype == PRESENCE_SERVICE_TYPE:
self._valid = 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.")
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 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 _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 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 _service_disappeared_cb(self, object_path):
gobject.idle_add(self._emit_service_disappeared_signal, object_path)
def _emit_joined_activity_signal(self, object_path):
self.emit('JoinedActivity', self._ps_new_object(object_path))
return False
def _joined_activity_cb(self, object_path):
gobject.idle_add(self._emit_joined_activity_signal, object_path)
def _emit_left_activity_signal(self, object_path):
self.emit('LeftActivity', self._ps_new_object(object_path))
return False
def _left_activity_cb(self, object_path):
gobject.idle_add(self._emit_left_activity_signal, object_path)
def getProperties(self):
return self._buddy.getProperties()
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 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

@ -1,6 +1,7 @@
sugardir = $(pythondir)/sugar/presence
sugar_PYTHON = \
__init__.py \
Activity.py \
Buddy.py \
PresenceService.py \
Service.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
def _get_local_ip_address(ifname):
"""Call Linux specific bits to retrieve our own IP address."""
import socket
import sys
import fcntl
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 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
import dbus, dbus.glib, dbus.dbus_bindings, gobject
import Buddy, Service, Activity
class ObjectCache(object):
def __init__(self):
self._cache = {}
def get(self, object_path):
try:
return self._cache[object_path]
except KeyError:
return None
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])),
'BuddyDisappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'ServiceAppeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
'ServiceDisappeared': (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]))
'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)
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
self._lock = threading.Lock()
self._started = False
# interface -> IP address: interfaces we've gotten events on so far
self._local_addrs = {}
# nick -> Buddy: buddies we've found
self._buddies = {}
# Our owner object
self._owner = None
# activity ID -> Service: services grouped by activity ID
self._activity_services = {}
# All the mdns service types we care about
self._allowed_service_types = [] # Short service type
# Keep track of stuff we're already browsing with ZC
self._service_type_browsers = {}
self._service_browsers = {}
self._resolve_queue = [] # Track resolve requests
# Resolved service list
self._service_advs = []
self._bus = dbus.SystemBus()
self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME,
avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
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:
return None
def _del_object(self, object_path):
# FIXME
pass
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 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
def _emit_buddy_appeared_signal(self, object_path):
self.emit('BuddyAppeared', self._new_object(object_path))
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
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
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)
def _buddy_appeared_cb(self, op):
gobject.idle_add(self._emit_buddy_appeared_signal, op)
def _emit_buddy_disappeared_signal(self, object_path):
self.emit('BuddyDisappeared', self._ps_new_object(object_path))
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 _buddy_disappeared_cb(self, object_path):
gobject.idle_add(self._emit_buddy_disappeared_signal, object_path)
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))
def _emit_service_appeared_signal(self, object_path):
self.emit('ServiceAppeared', self._ps_new_object(object_path))
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
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)
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_glue(self, interface, protocol, name, stype, domain, flags):
gobject.idle_add(self._service_disappeared_cb, interface, protocol, name, stype, domain, flags)
def _service_disappeared_cb(self, object_path):
gobject.idle_add(self._emit_service_disappeared_signal, object_path)
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
def _emit_activity_appeared_signal(self, object_path):
self.emit('ActivityAppeared', self._ps_new_object(object_path))
return False
# 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)
def _activity_appeared_cb(self, object_path):
gobject.idle_add(self._emit_activity_appeared_signal, object_path)
self._service_browsers[(interface, protocol, stype, domain)] = browser_obj
def _emit_activity_disappeared_signal(self, object_path):
self.emit('ActivityDisappeared', self._ps_new_object(object_path))
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
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)
# Are we already browsing this domain?
if self._service_type_browsers.has_key((interface, protocol, domain)):
return
def getBuddies(self):
resp = self._ps.getBuddies()
buddies = []
for item in resp:
buddies.append(self._new_object(item))
return buddies
# Start browsing this domain for the services its members offer
def getBuddyByName(self, name):
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
buddy_op = self._ps.getBuddyByName(name)
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))
def getBuddyByAddress(self, addr):
try:
buddy_op = self._ps.getBuddyByAddress(addr)
except dbus_bindings.DBusException:
return None
return self._new_object(buddy_op)
def getOwner(self):
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()
owner_op = self._ps.getOwner()
except dbus_bindings.DBusException:
return None
return self._new_object(buddy_op)

@ -1,394 +1,25 @@
import avahi
from sugar import util
import dbus
import gobject
import dbus, dbus_bindings
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()
class Service(gobject.GObject):
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])
_PRESENCE_SERVICE = "org.laptop.Presence"
_SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service"
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 __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)
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.")
def object_path(self):
return self._object_path
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
def getProperties(self):
return self._service.getProperties()
if activity_id is not None:
name = compose_service_name(name, activity_id)
return Service(name, stype, domain, address=address,
port=port, properties=properties)
_ACTIVITY_ID_TAG = "ActivityID"
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.")
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.")
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).")
(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)
# 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)

Loading…
Cancel
Save