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.py313
1 files changed, 172 insertions, 141 deletions
diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py
index ec0f9a3..f55547c 100644
--- a/tutorius/TProbe.py
+++ b/tutorius/TProbe.py
@@ -1,4 +1,5 @@
import logging
+LOGGER = logging.getLogger("sugar.tutorius.TProbe")
import os
import gobject
@@ -7,10 +8,12 @@ import dbus
import dbus.service
import cPickle as pickle
-import sugar.tutorius.addon as addon
-from sugar.tutorius.services import ObjectStore
+from . import addon
+from .services import ObjectStore
+from .properties import TPropContainer
+from .dbustools import remote_call, save_args
import copy
"""
@@ -24,25 +27,15 @@ import copy
-------------------- ----------
"""
+#TODO Add stub error handling for remote calls in the classes so that it will
+# be clearer how errors can be handled in the future.
+
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):
@@ -52,9 +45,9 @@ class TProbe(dbus.service.Object):
@param activity_name unique activity_id
@param activity activity reference, must be a gtk container
"""
- logging.debug("TProbe :: Creating TProbe for %s (%d)", activity_name, os.getpid())
- logging.debug("TProbe :: Current gobject context: %s", str(gobject.main_context_default()))
- logging.debug("TProbe :: Current gobject depth: %s", str(gobject.main_depth()))
+ LOGGER.debug("TProbe :: Creating TProbe for %s (%d)", activity_name, os.getpid())
+ LOGGER.debug("TProbe :: Current gobject context: %s", str(gobject.main_context_default()))
+ LOGGER.debug("TProbe :: Current gobject depth: %s", str(gobject.main_depth()))
# 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
@@ -157,38 +150,24 @@ class TProbe(dbus.service.Object):
in_signature='s', out_signature='s')
def subscribe(self, pickled_event):
"""
- Subscribe to a Gtk Widget Event
- @param pickled_event string pickled Event
+ Subscribe to an Event
+ @param pickled_event string pickled EventFilter
@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
+ #TODO Perform event unmapping once Tutorials use abstract events
+ # instead of concrete EventFilters that are tied to their
+ # implementation.
+ eventfilter = pickle.loads(str(pickled_event))
# 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)
+ self.notify(eventfilter)
eventfilter.install_handlers(callback, activity=self._activity)
- name = self._generate_event_reference(event)
+ name = self._generate_event_reference(eventfilter)
self._subscribedEvents[name] = eventfilter
return name
@@ -215,7 +194,12 @@ class TProbe(dbus.service.Object):
# The actual method we will call on the probe to send events
def notify(self, event):
- self.eventOccured(pickle.dumps(event))
+ LOGGER.debug("TProbe :: notify event %s", str(event))
+ #Check that this event is even allowed
+ if event in self._subscribedEvents.values():
+ self.eventOccured(pickle.dumps(event))
+ else:
+ raise RuntimeWarning("Attempted to raise an unregistered event")
# Return a unique name for this action
def _generate_action_reference(self, action):
@@ -232,11 +216,14 @@ class TProbe(dbus.service.Object):
# 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
+ name = event.__class__.__name__
+ #Keep the counter to avoid looping all the time
+ suffix = getattr(self, '_event_ref_suffix', 0 ) + 1
while self._subscribedEvents.has_key(name+str(suffix)):
suffix += 1
+
+ #setattr(self, '_event_ref_suffix', suffix)
return name + str(suffix)
@@ -246,108 +233,98 @@ class ProbeProxy:
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)
- void update(Action action)
- void uninstall(Action action)
- void uninstall_all()
- string subscribe(Event event, callable callback)
- void unsubscribe(Event event, callable callback)
- void unsubscribe_all()
"""
def __init__(self, activityName):
"""
Constructor
- @param activityName unique activity id
+ @param activityName unique activity id. Must be a valid dbus bus name.
"""
- logging.debug("ProbeProxy :: Creating ProbeProxy for %s (%d)", activityName, os.getpid())
- logging.debug("ProbeProxy :: Current gobject context: %s", str(gobject.main_context_default()))
- logging.debug("ProbeProxy :: Current gobject depth: %s", str(gobject.main_depth()))
+ LOGGER.debug("ProbeProxy :: Creating ProbeProxy for %s (%d)", activityName, os.getpid())
+ LOGGER.debug("ProbeProxy :: Current gobject context: %s", str(gobject.main_context_default()))
+ LOGGER.debug("ProbeProxy :: Current gobject depth: %s", str(gobject.main_depth()))
bus = dbus.SessionBus()
self._object = bus.get_object(activityName, "/tutorius/Probe")
self._probe = dbus.Interface(self._object, "org.tutorius.ProbeInterface")
self._actions = {}
- self._events = {}
# We keep those two data structures to be able to have multiple callbacks
# for the same event and be able to remove them independently
+ # _subscribedEvents holds a list of callback addresses's for each event
+ # _registeredCallbacks holds the functions to call for each address
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")
-
+
+ self._object.connect_to_signal("eventOccured", self._handle_signal, dbus_interface="org.tutorius.ProbeInterface")
+
+ def _handle_signal(self, pickled_event):
+ event = pickle.loads(str(pickled_event))
+ LOGGER.debug("ProbeProxy :: Received Event : %s %s", str(event), str(event._props.items()))
+
+ LOGGER.debug("ProbeProxy :: Currently %d events registered", len(self._registeredCallbacks))
+ if self._registeredCallbacks.has_key(event):
+ for callback in self._registeredCallbacks[event].values():
+ callback(event)
+ else:
+ for event in self._registeredCallbacks.keys():
+ LOGGER.debug("==== %s", str(event._props.items()))
+ LOGGER.debug("ProbeProxy :: Event does not appear to be registered")
+
def isAlive(self):
try:
return self._probe.ping() == "alive"
except:
return False
- def install(self, action):
+ def __update_action(self, action, address):
+ LOGGER.debug("ProbeProxy :: Updating action %s with address %s", str(action), str(address))
+ self._actions[action] = str(address)
+
+ def __clear_action(self, action):
+ self._actions.pop(action, None)
+
+ def install(self, action, block=False):
"""
Install an action on the TProbe's activity
@param action Action to install
+ @param block Force a synchroneous dbus call if True
@return None
"""
- address = str(self._probe.install(pickle.dumps(action)))
- self._actions[action] = address
+ return remote_call(self._probe.install, (pickle.dumps(action),),
+ save_args(self.__update_action, action),
+ block=block)
- def update(self, action):
+ def update(self, action, newaction, block=False):
"""
Update an already installed action's properties and run it again
@param action Action to update
+ @param newaction Action to update it with
+ @param block Force a synchroneous dbus call if True
@return None
"""
+ #TODO review how to make this work well
if not action in self._actions:
raise RuntimeWarning("Action not installed")
- return
- self._probe.update(self._actions[action], pickle.dumps(action._props))
+ #TODO Check error handling
+ return remote_call(self._probe.update, (self._actions[action], pickle.dumps(newaction._props)), block=block)
- def uninstall(self, action):
+ def uninstall(self, action, block=False):
"""
Uninstall an installed action
@param action Action to uninstall
+ @param block Force a synchroneous dbus call if True
"""
if action in self._actions:
- self._probe.uninstall(self._actions.pop(action))
-
- def uninstall_all(self):
- """
- Uninstall all installed actions
- @return None
- """
- for action in self._actions.keys():
- self.uninstall(action)
-
- 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
- if (event, callback) in self._events:
- raise RuntimeError("event already registered for callback")
- return
+ remote_call(self._probe.uninstall,(self._actions.pop(action),), block=block)
+ def __update_event(self, event, callback, address):
+ LOGGER.debug("ProbeProxy :: Registered event %s with address %s", str(hash(event)), str(address))
# 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)))
-
- self._events[(event, callback)] = address
+ address = str(address)
# We use the event object as a key
if not self._registeredCallbacks.has_key(event):
@@ -371,19 +348,8 @@ class ProbeProxy:
return address
- def unsubscribe(self, event, callback):
- """
- Unregister an event listener
- @param address identifier given by subscribe()
- @return None
- """
- if not (event, callback) in self._events:
- raise RuntimeWarning("callback/event not subscribed")
- return
-
- address = self._events.pop((event, callback))
- self._probe.unsubscribe()
-
+ def __clear_event(self, address):
+ LOGGER.debug("ProbeProxy :: Unregistering adress %s", str(address))
# Cleanup everything
if self._subscribedEvents.has_key(address):
event = self._subscribedEvents[address]
@@ -396,22 +362,69 @@ class ProbeProxy:
self._registeredCallbacks.pop(event)
self._subscribedEvents.pop(address)
+ else:
+ LOGGER.debug("ProbeProxy :: unsubsribe address %s inconsistency : not registered", address)
+
+ def subscribe(self, event, callback, block=True):
+ """
+ Register an event listener
+ @param event Event to listen for
+ @param callback callable that will be called when the event occurs
+ @param block Force a synchroneous dbus call if True (Not allowed yet)
+ @return address identifier used for unsubscribing
+ """
+ LOGGER.debug("ProbeProxy :: Registering event %s", str(hash(event)))
+ if not block:
+ raise RuntimeError("This function does not allow non-blocking mode yet")
- def unsubscribe_all(self):
+ # 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
+ return remote_call(self._probe.subscribe, (pickle.dumps(event),),
+ save_args(self.__update_event, event, callback),
+ block=block)
+
+ def unsubscribe(self, address, block=True):
"""
- Unregister all event listeners
+ Unregister an event listener
+ @param address identifier given by subscribe()
+ @param block Force a synchroneous dbus call if True
@return None
"""
- for event, callback in self._events.keys():
- self.unsubscribe(event, callback)
+ LOGGER.debug("ProbeProxy :: Unregister adress %s issued", str(address))
+ if address in self._subscribedEvents.keys():
+ remote_call(self._probe.unsubscribe, (address,),
+ return_cb=save_args(self.__clear_event, address),
+ block=block)
+ else:
+ LOGGER.debug("ProbeProxy :: unsubsribe address %s failed : not registered", address)
+
+ def detach(self, block=False):
+ """
+ Detach the ProbeProxy from it's TProbe. All installed actions and
+ subscribed events should be removed.
+ """
+ for action_addr in self._actions.keys():
+ self.uninstall(action_addr, block)
+
+ for address in self._subscribedEvents.keys():
+ self.unsubscribe(address, block)
+
class ProbeManager(object):
"""
The ProbeManager provides multiplexing across multiple activity ProbeProxies
For now, it only handles one at a time, though.
+ Actually it doesn't do much at all. But it keeps your encapsulation happy
"""
- def __init__(self):
+ def __init__(self, proxy_class=ProbeProxy):
+ """Constructor
+ @param proxy_class Class to use for creating Proxies to activities.
+ The class should support the same interface as ProbeProxy. Exists
+ to make this class unit-testable by replacing the Proxy with a mock
+ """
+ self._ProxyClass = proxy_class
self._probes = {}
self._current_activity = None
@@ -427,9 +440,9 @@ class ProbeManager(object):
def attach(self, activity_id):
if activity_id in self._probes:
raise RuntimeWarning("Activity already attached")
- return
- self._probes[activity_id] = ProbeProxy(activity_id)
+ self._probes[activity_id] = self._ProxyClass(activity_id)
+ #TODO what do we do with this? Raise something?
if self._probes[activity_id].isAlive():
print "Alive!"
else:
@@ -438,48 +451,66 @@ class ProbeManager(object):
def detach(self, activity_id):
if activity_id in self._probes:
probe = self._probes.pop(activity_id)
- probe.unsubscribe_all()
- probe.uninstall_all()
-
- def install(self, action):
- if self.currentActivity:
- return self._probes[self.currentActivity].install(action)
- else:
- raise RuntimeWarning("No activity attached")
+ probe.detach()
+ if self._current_activity == activity_id:
+ self._current_activity = None
- def update(self, action):
+ def install(self, action, block=False):
+ """
+ Install an action on the current activity
+ @param action Action to install
+ @param block Force a synchroneous dbus call if True
+ @return None
+ """
if self.currentActivity:
- return self._probes[self.currentActivity].update(action)
+ return self._probes[self.currentActivity].install(action, block)
else:
raise RuntimeWarning("No activity attached")
- def uninstall(self, action):
+ def update(self, action, newaction, block=False):
+ """
+ Update an already installed action's properties and run it again
+ @param action Action to update
+ @param newaction Action to update it with
+ @param block Force a synchroneous dbus call if True
+ @return None
+ """
if self.currentActivity:
- return self._probes[self.currentActivity].uninstall(action)
+ return self._probes[self.currentActivity].update(action, newaction, block)
else:
raise RuntimeWarning("No activity attached")
- def uninstall_all(self):
+ def uninstall(self, action, block=False):
+ """
+ Uninstall an installed action
+ @param action Action to uninstall
+ @param block Force a synchroneous dbus call if True
+ """
if self.currentActivity:
- return self._probes[self.currentActivity].uninstall_all()
+ return self._probes[self.currentActivity].uninstall(action, block)
else:
raise RuntimeWarning("No activity attached")
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
+ """
if self.currentActivity:
return self._probes[self.currentActivity].subscribe(event, callback)
else:
raise RuntimeWarning("No activity attached")
- def unsubscribe(self, event, callback):
- if self.currentActivity:
- return self._probes[self.currentActivity].unsubscribe(event, callback)
- else:
- raise RuntimeWarning("No activity attached")
-
- def unsubscribe_all(self):
+ def unsubscribe(self, address):
+ """
+ Unregister an event listener
+ @param address identifier given by subscribe()
+ @return None
+ """
if self.currentActivity:
- return self._probes[self.currentActivity].unsubscribe_all()
+ return self._probes[self.currentActivity].unsubscribe(address)
else:
raise RuntimeWarning("No activity attached")