From c2f9d4c2067db08b4ca3c02a76797ae72faa5e6a Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 19 Jul 2006 15:42:34 +0000 Subject: Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar --- (limited to 'shell') diff --git a/shell/PresenceService/Activity.py b/shell/PresenceService/Activity.py index 5fbe8a5..f1e6f7c 100644 --- a/shell/PresenceService/Activity.py +++ b/shell/PresenceService/Activity.py @@ -1,5 +1,7 @@ import dbus +class NotFoundError(Exception): + pass class ActivityDBusHelper(dbus.service.Object): def __init__(self, parent, bus_name, object_path): @@ -8,22 +10,129 @@ class ActivityDBusHelper(dbus.service.Object): self._object_path = object_path dbus.service.Object.__init__(self, bus_name, self._object_path) + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="s", out_signature="ao") + def getServicesOfType(self, stype): + services = self._parent.get_services_of_type(stype) + if not services: + raise NotFoundError("Not found") + ret = [] + for serv in services: + ret.append(serv.object_path()) + return ret + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="ao") + def getServices(self): + services = self._parent.get_services() + if not services: + raise NotFoundError("Not found") + ret = [] + for serv in services: + ret.append(serv.object_path()) + return ret + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="s") + def getId(self): + return self._parent.get_id() + + @dbus.service.method(BUDDY_DBUS_INTERFACE, + in_signature="", out_signature="ao") + def getJoinedBuddies(self): + buddies = self._parent.get_joined_buddies() + if not buddies: + raise NotFoundError("Not found") + ret = [] + for buddy in buddies: + ret.append(buddy.object_path()) + return ret + + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") + def ServiceAppeared(self, object_path): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") + def ServiceDisappeared(self, object_path): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") + def BuddyJoined(self, object_path): + pass + + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") + def BuddyLeft(self, object_path): + pass + class Activity(object): def __init__(self, bus_name, object_id, activity_id): + if not activity_id: + raise ValueError("Service must have a valid Activity ID") self._activity_id = activity_id self._buddies = [] - self._services = {} # service type -> Service + self._services = {} # service type -> list of Services + self._services[service.get_type()] = [] self._object_id = object_id self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id self._dbus_helper = ActivityDBusHelper(self, bus_name, self._object_path) + def object_path(self): + return dbus.ObjectPath(self._object_path) + def get_id(self): return self._activity_id - def get_service_of_type(self, stype): + def get_services(self): + return self._services.values() + + def get_services_of_type(self, stype): if self._services.has_key(stype): return self._services[stype] return None + + def get_joined_buddies(self): + buddies = [] + for serv in self._services.values(): + buddies.append(serv.get_owner()) + return buddies + + def add_service(self, service): + stype = service.get_type() + if not self._services.has_key(stype): + self._services[stype] = [] + + # Send out the BuddyJoined signal if this is the first + # service from the buddy that we've seen + buddies = self.get_joined_buddies() + serv_owner = service.get_owner() + if serv_owner and serv_owner not in buddies: + self._dbus_helper.BuddyJoined(serv_owner.object_path()) + serv_owner.add_activity(self) + + if not service in self._services[stype]: + self._services[stype].append(service) + self._dbus_helper.ServiceAppeared(service.object_path()) + + def remove_service(self, service): + stype = service.get_type() + if not self._services.has_key(stype): + return + self._services[stype].remove(service) + self._dbus_helper.ServiceDisappeared(service.object_path()) + if len(self._services[stype]) == 0: + del self._services[stype] + + # Send out the BuddyLeft signal if this is the last + # service from the buddy + buddies = self.get_joined_buddies() + serv_owner = service.get_owner() + if serv_owner and serv_owner not in buddies: + serv_owner.remove_activity(self) + self._dbus_helper.BuddyLeft(serv_owner.object_path()) diff --git a/shell/PresenceService/Buddy.py b/shell/PresenceService/Buddy.py index c3d17e1..ca2b566 100644 --- a/shell/PresenceService/Buddy.py +++ b/shell/PresenceService/Buddy.py @@ -3,10 +3,8 @@ import logging import gtk import gobject +import dbus, dbus.service -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" @@ -21,23 +19,28 @@ class BuddyDBusHelper(dbus.service.Object): self._object_path = object_path dbus.service.Object.__init__(self, bus_name, self._object_path) - @dbus.service.signal(BUDDY_DBUS_INTERFACE) + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") def ServiceAppeared(self, object_path): pass - @dbus.service.signal(BUDDY_DBUS_INTERFACE) + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") def ServiceDisappeared(self, object_path): pass - @dbus.service.signal(BUDDY_DBUS_INTERFACE) + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="") def IconChanged(self): pass - @dbus.service.signal(BUDDY_DBUS_INTERFACE) + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") def JoinedActivity(self, object_path): pass - @dbus.service.signal(BUDDY_DBUS_INTERFACE) + @dbus.service.signal(BUDDY_DBUS_INTERFACE, + signature="o") def LeftActivity(self, object_path): pass @@ -77,7 +80,7 @@ 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): + def __init__(self, bus_name, object_id, service, owner=False): if not bus_name: raise ValueError("DBus bus name must be valid") if not object_id or type(object_id) != type(1): @@ -91,18 +94,20 @@ class Buddy(object): self._valid = False self._icon = None self._icon_tries = 0 - self._owner = False - self.add_service(service) + self._owner = owner 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) + self.add_service(service) + def object_path(self): - return self._object_path + return dbus.ObjectPath(self._object_path) def _request_buddy_icon_cb(self, result_status, response, user_data): """Callback when icon request has completed.""" + from sugar.p2p import network icon = response service = user_data if result_status == network.RESULT_SUCCESS: @@ -120,6 +125,7 @@ class Buddy(object): def _request_buddy_icon(self, service): """Contact the buddy to retrieve the buddy icon.""" + from sugar.p2p import Stream 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) @@ -141,21 +147,7 @@ class Buddy(object): 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())) + service.set_owner(self) if stype == PRESENCE_SERVICE_TYPE: # A buddy isn't valid until its official presence @@ -163,8 +155,26 @@ class Buddy(object): self._valid = True print 'Requesting buddy icon %s' % self._nick_name self._request_buddy_icon(service) + + if self._valid: + self._dbus_helper.ServiceAppeared(service.object_path()) return True + def add_activity(self, activity): + actid = activity.get_id() + if activity in self._activities.values(): + raise RuntimeError("Tried to add activity twice") + found = False + for serv in self._services.values(): + if serv.get_activity_id() == activity.get_id(): + found = True + break + if not found: + raise RuntimeError("Tried to add activity for which we had no service") + self._activities[actid] = activity + print "Buddy (%s) joined activity %s." % (self._nick_name, actid) + self._dbus_helper.JoinedActivity(activity.object_path()) + def remove_service(self, service): """Remove a service from a buddy; ie, the activity was closed or the buddy went away.""" @@ -175,25 +185,20 @@ class Buddy(object): stype = service.get_type() if self._services.has_key(stype): if self._valid: - self._dbus_helper.ServiceDisappeared(dbus.ObjectPath(service.object_path())) + self._dbus_helper.ServiceDisappeared(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 remove_activity(self, activity): + actid = activity.get_id() + if not self._activities.has_key(actid): + return + del self._activities[actid] + print "Buddy (%s) left activity %s." % (self._nick_name, actid) + self._dbus_helper.LeftActivity(activity.object_path()) + 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.""" @@ -248,6 +253,5 @@ class Buddy(object): 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 __init__(self, bus_name, object_id, service): + Buddy.__init__(self, bus_name, object_id, service, owner=True) diff --git a/shell/PresenceService/PresenceService.py b/shell/PresenceService/PresenceService.py index cd5bd7f..657bb92 100644 --- a/shell/PresenceService/PresenceService.py +++ b/shell/PresenceService/PresenceService.py @@ -64,13 +64,54 @@ class ServiceAdv(object): self._resolved = resolved -_PRESENCE_INTERFACE = "org.laptop.Presence" - -class PresenceService(dbus.service.Object): +_PRESENCE_SERVICE = "org.laptop.Presence" +_PRESENCE_DBUS_INTERFACE = "org.laptop.Presence" +_PRESENCE_OBJECT_PATH = "/org/laptop/Presence" + +class PresenceServiceDBusHelper(dbus.service.Object): + def __init__(self, parent, bus_name): + self._parent = parent + self._bus_name = bus_name + dbus.service.Object.__init__(self, bus_name, _PRESENCE_OBJECT_PATH) + + @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, + signature="o") + def BuddyAppeared(self, object_path): + pass + + @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, + signature="o") + def BuddyDisappeared(self, object_path): + pass + + @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, + signature="o") + def ServiceAppeared(self, object_path): + pass + + @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, + signature="o") + def ServiceDisappeared(self, object_path): + pass + + @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, + signature="o") + def ActivityAppeared(self, object_path): + pass + + @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, + signature="o") + def ActivityDisappeared(self, object_path): + pass + + +class PresenceService(object): def __init__(self): # interface -> IP address: interfaces we've gotten events on so far self._local_addrs = {} + self._next_object_id = 0 + # Our owner object self._owner = None @@ -86,19 +127,19 @@ class PresenceService(dbus.service.Object): # Resolved service list self._service_advs = [] + # Service types we care about resolving + self._registered_service_types = [] + # Set up the dbus service we provide session_bus = dbus.SessionBus() - bus_name = dbus.service.BusName('org.laptop.Presence', bus=session_bus) - dbus.service.Object.__init__(self, bus_name, '/org/laptop/Presence') + self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=session_bus) + self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name) # Connect to Avahi for mDNS stuff self._system_bus = dbus.SystemBus() - self._mdns_service = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, + self._mdns_service = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) - # Start browsing the local mDNS domain - self._start() - def _start(self): # Always browse .local self._new_domain_cb(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local") @@ -108,6 +149,11 @@ class PresenceService(dbus.service.Object): db = dbus.Interface(self._system_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_next_object_id(self): + """Increment and return the object ID counter.""" + self._next_object_id = self._next_object_id + 1 + return self._next_object_id + 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 = [] @@ -125,6 +171,110 @@ class PresenceService(dbus.service.Object): adv_list.append(adv) return adv_list + 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._dbus_helper.ServiceAppeared(service.object_path()) + except KeyError: + # Should this service mark the owner? + owner_nick = env.get_nick_name() + publisher_addr = service.get_publisher_address() + objid = self._get_next_object_id() + if name == owner_nick and publisher_addr in self._local_addrs.values(): + buddy = Buddy.Owner(self._bus_name, objid, service) + self._owner = buddy + logging.debug("Owner is '%s'." % name) + else: + buddy = Buddy.Buddy(self._bus_name, objid, service) + self._buddies[name] = buddy + self._dbus_helper.ServiceAppeared(service.object_path()) + if not buddy_was_valid and buddy.is_valid(): + self._dbus_helper.BuddyAppeared(buddy.object_path()) + return buddy + + def _handle_new_activity_service(self, service): + # If the serivce is an activity service, merge it into our activities list + actid = service.get_activity_id() + if not actid: + return + activity = None + if not self._activities.has_key(actid): + objid = self._get_next_object_id() + activity = Activity.Activity(self._bus_name, objid, service) + self._activities[actid] = activity + self._dbus_helper.ActivityAppeared(activity.object_path()) + else: + activity = self._activities[actid] + + if activity: + activity.add_service(service) + + def _handle_remove_activity_service(self, service): + actid = service.get_activity_id() + if not actid: + return + if not self._activities.has_key(actid): + return + activity = self._activities[actid] + activity.remove_service(service) + if len(activity.get_services()) == 0: + # Kill the activity + self._dbus_helper.ActivityDisappeared(activity.object_path()) + del self._activities[actid] + + 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)) + + # 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 + objid = self._get_next_object_id() + service = Service.Service(self._bus_name, objid, name=full_name, + stype=stype, domain=domain, address=address, port=port, + properties=txt) + adv.set_service(service) + + # Merge the service into our buddy and activity lists, if needed + buddy = self._handle_new_service_for_buddy(service) + if buddy and service.get_activity_id(): + self._handle_new_activity_service(service) + + 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._mdns_service.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)) @@ -141,7 +291,7 @@ class PresenceService(dbus.service.Object): # 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) + ifname = self._mdns_service.GetNetworkInterfaceNameByIndex(interface) if ifname: addr = _get_local_ip_address(ifname) if addr: @@ -150,14 +300,9 @@ class PresenceService(dbus.service.Object): # Decompose service name if we can (actid, buddy_name) = Service._decompose_service_name(full_name) - # 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): + if actid is not None or stype in self._registered_service_types: resolve = True if resolve and not adv in self._resolve_queue: self._resolve_queue.append(adv) @@ -197,11 +342,11 @@ class PresenceService(dbus.service.Object): pass else: buddy.remove_service(service) - self.emit('service-disappeared', buddy, service) + self._dbus_helper.ServiceDisappeared(service.object_path()) + self._handle_remove_activity_service(service) if not buddy.is_valid(): - self.emit("buddy-disappeared", buddy) + self._dbus_helper.BuddyDisappeared(buddy.object_path()) del self._buddies[buddy_name] - self._handle_remove_service_for_activity(service, buddy) return False @@ -257,3 +402,13 @@ class PresenceService(dbus.service.Object): def _new_domain_cb_glue(self, interface, protocol, domain, flags=0): gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags) + + + +def main(): + import gtk + ps = PresenceService() + gtk.main() + +if __name__ == "__main__": + main() diff --git a/shell/PresenceService/Service.py b/shell/PresenceService/Service.py index 6475567..2e51c07 100644 --- a/shell/PresenceService/Service.py +++ b/shell/PresenceService/Service.py @@ -63,44 +63,6 @@ def is_multicast_address(address): return True return False -def deserialize(sdict): - try: - name = sdict['name'] - if type(name) != type(u""): - raise ValueError("name must be unicode.") - stype = sdict['stype'] - if type(stype) != type(u""): - raise ValueError("type must be unicode.") - domain = sdict['domain'] - if type(domain) != type(u""): - raise ValueError("domain must be unicode.") - 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""): - raise ValueError("address must be unicode.") - except KeyError: - pass - - activity_id = None - try: - activity_id = sdict['activity_id'] - if type(activity_id) != type(u""): - raise ValueError("activity id must be unicode.") - 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) - _ACTIVITY_ID_TAG = "ActivityID" SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service" @@ -197,29 +159,23 @@ class Service(object): if actid and not self._properties.has_key(_ACTIVITY_ID_TAG): self._properties[_ACTIVITY_ID_TAG] = actid + self._owner = None + # register ourselves with dbus self._object_id = object_id self._object_path = "/org/laptop/Presence/Services/%d" % self._object_id self._dbus_helper = ServiceDBusHelper(self, bus_name, self._object_path) def object_path(self): - return self._object_path + return dbus.ObjectPath(self._object_path) - 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_owner(self): + return self._owner + + def set_owner(self, owner): + if self._owner is not None: + raise RuntimeError("Can only set a service's owner once") + self._owner = owner def get_name(self): """Return the service's name, usually that of the @@ -256,9 +212,7 @@ class Service(object): 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 + # Set key/value pairs on internal property list for key, value in props.items(): tmp_key = key tmp_val = value diff --git a/shell/PresenceService/__init__.py b/shell/PresenceService/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/shell/PresenceService/__init__.py diff --git a/shell/Shell.py b/shell/Shell.py index b0e04d6..416e655 100755 --- a/shell/Shell.py +++ b/shell/Shell.py @@ -14,6 +14,7 @@ from sugar import env from sugar.activity import Activity from PeopleWindow import PeopleWindow from Owner import ShellOwner +from PresenceService import PresenceService class ShellDbusService(dbus.service.Object): def __init__(self, shell, bus_name): @@ -46,6 +47,7 @@ class Shell: bus_name = dbus.service.BusName('com.redhat.Sugar.Shell', bus=session_bus) ShellDbusService(self, bus_name) + self._ps = PresenceService.PresenceService() self._owner = ShellOwner() self._registry = ActivityRegistry() -- cgit v0.9.1