Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar
This commit is contained in:
		
						commit
						e348de9c7e
					
				| @ -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): | ||||
|  | ||||
							
								
								
									
										99
									
								
								sugar/presence/Activity.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								sugar/presence/Activity.py
									
									
									
									
									
										Normal file
									
								
							| @ -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])), | ||||
| 		'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 | ||||
|  | ||||
| @ -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 | ||||
| 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() | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Marco Pesenti Gritti
						Marco Pesenti Gritti