diff options
Diffstat (limited to 'shell/PresenceService/Buddy.py')
-rw-r--r-- | shell/PresenceService/Buddy.py | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/shell/PresenceService/Buddy.py b/shell/PresenceService/Buddy.py new file mode 100644 index 0000000..c3d17e1 --- /dev/null +++ b/shell/PresenceService/Buddy.py @@ -0,0 +1,253 @@ +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" +BUDDY_DBUS_INTERFACE = "org.laptop.Presence.Buddy" + +class NotFoundError(Exception): + pass + +class BuddyDBusHelper(dbus.service.Object): + def __init__(self, parent, bus_name, object_path): + self._parent = parent + self._bus_name = bus_name + self._object_path = object_path + dbus.service.Object.__init__(self, bus_name, self._object_path) + + @dbus.service.signal(BUDDY_DBUS_INTERFACE) + def ServiceAppeared(self, object_path): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE) + def ServiceDisappeared(self, object_path): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE) + def IconChanged(self): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE) + def JoinedActivity(self, object_path): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE) + def LeftActivity(self, object_path): + pass + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="ay") + def getIcon(self): + icon = self._parent.get_icon() + if not icon: + return "" + return icon + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="o") + def getServiceOfType(self, stype): + service = self._parent.get_service_of_type(stype) + if not service: + raise NotFoundError("Not found") + return service + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="ao") + def getJoinedActivities(self): + acts = [] + return acts + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="a{sv}") + def getProperties(self): + props = {} + props['name'] = self._parent.get_nick_name() + props['ip4_address'] = self._parent.get_address() + props['owner'] = self._parent.is_owner() + return props + + +class Buddy(object): + """Represents another person on the network and keeps track of the + activities and resources they make available for sharing.""" + + def __init__(self, bus_name, object_id, service): + if not bus_name: + raise ValueError("DBus bus name must be valid") + if not object_id or type(object_id) != type(1): + raise ValueError("object id must be a valid number") + + self._services = {} + self._activities = {} + + 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_id = object_id + self._object_path = "/org/laptop/Presence/Buddies/%d" % self._object_id + self._dbus_helper = BuddyDBusHelper(self, bus_name, self._object_path) + + def object_path(self): + return self._object_path + + 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) + 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) + 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._dbus_helper.ServiceAppeared(dbus.ObjectPath(service.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._dbus_helper.JoinedActivity(dbus.ObjectPath(activity.object_path())) + + 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._dbus_helper.ServiceDisappeared(dbus.ObjectPath(service.object_path())) + 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._dbus_helper.LeftActivity(dbus.ObjectPath(activity.object_path())) + + 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 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._dbus_helper.IconChanged() + + 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 |