import gobject import dbus import cPickle as pickle import sugar.tutorius.addon as addon from sugar.tutorius.services import ObjectStore import copy """ The TProbe module defines two connected classes, TProbe and ProbeProxy. -------------------- ---------- | ProbeProxy |----- DBus ---->| TProbe | -------------------- ---------- """ class TProbe(dbus.service.Object): """ Tutorius Probe Defines an entry point for Tutorius into activities that allows performing actions and registering events onto an activity via a DBUS Interface. Exposes the following dbus methods: void registered(string service) string ping() -> status string install(string action) -> address void update(string address, string action_props) void uninstall(string address) string subscribe(string pickled_event) -> address void unsubscribe(string address) Exposes the following dbus Events: eventOccured(event): """ def __init__(self, activity_name, activity): """ Create and register a TProbe for an activity. @param activity_name unique activity_id @param activity activity reference, must be a gtk container """ # Moving the ObjectStore assignment here, in the meantime # the reference to the activity shouldn't be share as a # global variable but passed by the Probe to the objects # that requires it self._activity = activity ObjectStore().activity = activity self._activity_name = activity_name self._session_bus = dbus.SessionBus() # Giving a new name because _name is already used by dbus self._name2 = dbus.service.BusName(activity_name, self._session_bus) dbus.service.Object.__init__(self, self._session_bus, "/tutorius/Probe") # Add the dictionary we will use to store which actions and events # are known self._installedActions = {} self._subscribedEvents = {} def start(self): """ Optional method to call if the probe is not inserted into an existing activity. Starts a gobject mainloop """ mainloop = gobject.MainLoop() print "Starting Probe for " + self._activity_name mainloop.run() @dbus.service.method("org.tutorius.ProbeInterface", in_signature='s', out_signature='') def registered(self, service): print ("Registered with: " + str(service)) @dbus.service.method("org.tutorius.ProbeInterface", in_signature='', out_signature='s') def ping(self): """ Allows testing the connection to a Probe @return string "alive" """ return "alive" # ------------------ Action handling -------------------------------------- @dbus.service.method("org.tutorius.ProbeInterface", in_signature='s', out_signature='s') def install(self, pickled_action): """ Install an action on the Activity @param pickled_action string pickled action @return string address of installed action """ loaded_action = pickle.loads(str(pickled_action)) action = addon.create(loaded_action.__class__.__name__) address = self._generate_action_reference(action) self._installedActions[address] = action if action._props: action._props.update(loaded_action._props) action.do() return address @dbus.service.method("org.tutorius.ProbeInterface", in_signature='ss', out_signature='') def update(self, address, action_props): """ Update an already registered action @param address string address returned by install() @param action_props pickled action properties @return None """ action = self._installedActions[address] if action._props: props = pickle.loads(str(action_props)) action._props.update(props) action.undo() action.do() @dbus.service.method("org.tutorius.ProbeInterface", in_signature='s', out_signature='') def uninstall(self, address): """ Uninstall an action @param address string address returned by install() @return None """ if self._installedActions.has_key(address): action = self._installedActions[address] action.undo() self._installedActions.pop(address) # ------------------ Event handling --------------------------------------- @dbus.service.method("org.tutorius.ProbeInterface", in_signature='s', out_signature='s') def subscribe(self, pickled_event): """ Subscribe to a Gtk Widget Event @param pickled_event string pickled Event @return string unique name of registered event """ event = pickle.loads(str(pickled_event)) # TODO elavoie 2009-07-25 Move to a reference counting implementation # to avoid duplicating eventfilters when the event signature is the # same # For now we will assume every probe is inserted in a GTK activity, # however, in the future this should be moved in a subclass eventfilter = addon.create("GtkWidgetEventFilter") # There might be a validation of the Address in source in the future # and a partial resolution to extract the object_id from the address eventfilter.object_id = event.source # TODO elavoie 2009-07-19 # There should be a type translation from a tutorius type # to a GTK type here eventfilter.event_name = event.type # The callback uses the event defined previously and each # successive call to subscribe will register a different # callback that references a different event def callback(*args): self.notify(event) eventfilter.install_handlers(callback, activity=self._activity) name = self._generate_event_reference(event) self._subscribedEvents[name] = eventfilter return name @dbus.service.method("org.tutorius.ProbeInterface", in_signature='s', out_signature='') def unsubscribe(self, address): """ Remove subscription to an event @param address string adress returned by subscribe() @return None """ if self._subscribedEvents.has_key(address): eventfilter = self._subscribedEvents[address] eventfilter.remove_handlers() self._subscribedEvents.pop(address) @dbus.service.signal("org.tutorius.ProbeInterface") def eventOccured(self, event): # We need no processing now, the signal will be sent # when the method exit pass # The actual method we will call on the probe to send events def notify(self, event): self.eventOccured(pickle.dumps(event)) # Return a unique name for this action def _generate_action_reference(self, action): # TODO elavoie 2009-07-25 Should return a universal address name = action.__class__.__name__ suffix = 1 while self._installedActions.has_key(name+str(suffix)): suffix += 1 return name + str(suffix) # Return a unique name for this event def _generate_event_reference(self, event): # TODO elavoie 2009-07-25 Should return a universal address name = event.type suffix = 1 while self._subscribedEvents.has_key(name+str(suffix)): suffix += 1 return name + str(suffix) class ProbeProxy: """ ProbeProxy is a Proxy class for connecting to a remote TProbe. It provides an object interface to the TProbe, which requires pickled strings, across a DBus communication. Public Methods: ProbeProxy(string activityName) :: Constructor string install(Action action) -> action address void update(string address, Action action) void uninstall(string address) string subscribe(Event event, callable callback) -> event address void unsubscribe(string address) """ def __init__(self, activityName): """ Constructor @param activityName unique activity id """ bus = dbus.SessionBus() self._object = bus.get_object(activityName, "/tutorius/Probe") self._probe = dbus.Interface(self._object, "org.tutorius.ProbeInterface") # We keep those two data structures to be able to have multiple callbacks # for the same event and be able to remove them independently self._subscribedEvents = {} self._registeredCallbacks = {} def _handle_signal(pickled_event): event = pickle.loads(str(pickled_event)) if self._registeredCallbacks.has_key(event): for callback in self._registeredCallbacks[event].itervalues(): callback(event) self._object.connect_to_signal("eventOccured", _handle_signal, dbus_interface="org.tutorius.ProbeInterface") def install(self, action): """ Install an action on the TProbe's activity @param action Action to install @return address identifier used for update and uninstall """ address = str(self._probe.install(pickle.dumps(action))) return address def update(self, address, action): """ Update an already installed action's properties and run it again @param address identifier returned by the action install @param action Action to get properties from @return None """ self._probe.update(address, pickle.dumps(action._props)) def uninstall(self, address): """ Uninstall an installd action @param address identifier returned by the action install @return None """ self._probe.uninstall(address) def subscribe(self, event, callback): """ Register an event listener @param event Event to listen for @param callback callable that will be called when the event occurs @return address identifier used for unsubscribing """ # TODO elavoie 2009-07-25 When we will allow for patterns both # for event types and sources, we will need to revise the lookup # mecanism for which callback function to call # Since multiple callbacks could be associated to the same # event signature, we will store multiple callbacks # in a dictionary indexed by the unique address # given for this subscribtion and access this # dictionary from another one indexed by event address = str(self._probe.subscribe(pickle.dumps(event))) # We use the event object as a key if not self._registeredCallbacks.has_key(event): self._registeredCallbacks[event] = {} # TODO elavoie 2009-07-25 decide on a proper exception # taxonomy if self._registeredCallbacks[event].has_key(address): # Oups, how come we have two similar addresses? # send the bad news! raise Exception("Probe subscribe exception, the following address already exists: " + str(address)) self._registeredCallbacks[event][address] = callback # We will keep another dictionary to remember the # event that was associated to this unique address # Let's copy to make sure that even if the event # passed in is modified later it won't screw up # our dictionary (python pass arguments by reference) self._subscribedEvents[address] = copy.copy(event) return address def unsubscribe(self, address): """ Unregister an event listener @param address identifier given by subscribe() @return None """ self._probe.unsubscribe(address) # Cleanup everything if self._subscribedEvents.has_key(address): event = self._subscribedEvents[address] if self._registeredCallbacks.has_key(event)\ and self._registeredCallbacks[event].has_key(address): self._registeredCallbacks[event].pop(address) if self._registeredCallbacks[event] == {}: self._registeredCallbacks.pop(event) self._subscribedEvents.pop(address)