Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/TProbe.py
diff options
context:
space:
mode:
Diffstat (limited to 'tutorius/TProbe.py')
-rw-r--r--tutorius/TProbe.py360
1 files changed, 360 insertions, 0 deletions
diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py
new file mode 100644
index 0000000..6dd3afb
--- /dev/null
+++ b/tutorius/TProbe.py
@@ -0,0 +1,360 @@
+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)
+
+