Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormike <michael.jmontcalm@gmail.com>2009-11-19 14:38:39 (GMT)
committer mike <michael.jmontcalm@gmail.com>2009-11-19 14:40:25 (GMT)
commit88b3f3099252353e22c536a68e15e2a2f9b10334 (patch)
treef9c754a9f348e5020687e705f40f426175375353
parent40f836b057896469bca69772d9fc7168b2f8c644 (diff)
LP 448319 : Addition of resource properties, insertion of BubbleMessageWImg from Dave, modification of Engine to get action_addresses
-rw-r--r--tutorius/TProbe.py34
-rw-r--r--tutorius/constraints.py39
-rw-r--r--tutorius/engine.py29
-rw-r--r--tutorius/overlayer.py192
-rw-r--r--tutorius/properties.py50
-rw-r--r--tutorius/translator.py204
-rw-r--r--tutorius/vault.py1
7 files changed, 522 insertions, 27 deletions
diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py
index 0c79690..b4b1826 100644
--- a/tutorius/TProbe.py
+++ b/tutorius/TProbe.py
@@ -297,14 +297,15 @@ class ProbeProxy:
except:
return False
- def __update_action(self, action, address):
+ def __update_action(self, action, callback, address):
LOGGER.debug("ProbeProxy :: Updating action %s with address %s", str(action), str(address))
self._actions[action] = str(address)
+ callback(address)
def __clear_action(self, action):
self._actions.pop(action, None)
- def install(self, action, block=False):
+ def install(self, action, callback, block=False):
"""
Install an action on the TProbe's activity
@param action Action to install
@@ -312,10 +313,10 @@ class ProbeProxy:
@return None
"""
return remote_call(self._probe.install, (pickle.dumps(action),),
- save_args(self.__update_action, action),
+ save_args(self.__update_action, action, callback),
block=block)
- def update(self, action, newaction, block=False):
+ def update(self, action_address, newaction, block=False):
"""
Update an already installed action's properties and run it again
@param action Action to update
@@ -324,19 +325,20 @@ class ProbeProxy:
@return None
"""
#TODO review how to make this work well
- if not action in self._actions:
+ if not action_address in self._actions.values():
raise RuntimeWarning("Action not installed")
#TODO Check error handling
- return remote_call(self._probe.update, (self._actions[action], pickle.dumps(newaction._props)), block=block)
+ return remote_call(self._probe.update, (action_address, pickle.dumps(newaction._props)), block=block)
- def uninstall(self, action, block=False):
+ def uninstall(self, action_address, 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:
- remote_call(self._probe.uninstall,(self._actions.pop(action),), block=block)
+ for (action, action_address) in self._actions.items():
+ remote_call(self._probe.uninstall,(action_address,), block=block)
+ del self._actions[action]
def __update_event(self, event, callback, address):
LOGGER.debug("ProbeProxy :: Registered event %s with address %s", str(hash(event)), str(address))
@@ -465,7 +467,7 @@ class ProbeManager(object):
currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity)
- def install(self, action, block=False):
+ def install(self, action, callback, block=False):
"""
Install an action on the current activity
@param action Action to install
@@ -473,31 +475,31 @@ class ProbeManager(object):
@return None
"""
if self.currentActivity:
- return self._first_proxy(self.currentActivity).install(action, block)
+ return self._first_proxy(self.currentActivity).install(action, callback, block)
else:
raise RuntimeWarning("No activity attached")
- def update(self, action, newaction, block=False):
+ def update(self, action_address, newaction, block=False):
"""
Update an already installed action's properties and run it again
- @param action Action to update
+ @param action_address 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._first_proxy(self.currentActivity).update(action, newaction, block)
+ return self._first_proxy(self.currentActivity).update(action_address, newaction, block)
else:
raise RuntimeWarning("No activity attached")
- def uninstall(self, action, block=False):
+ def uninstall(self, action_address, 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._first_proxy(self.currentActivity).uninstall(action, block)
+ return self._first_proxy(self.currentActivity).uninstall(action_address, block)
else:
raise RuntimeWarning("No activity attached")
diff --git a/tutorius/constraints.py b/tutorius/constraints.py
index 519bce8..cd71167 100644
--- a/tutorius/constraints.py
+++ b/tutorius/constraints.py
@@ -24,6 +24,8 @@ for some properties.
# For the File Constraint
import os
+# For the Resource Constraint
+import re
class ConstraintException(Exception):
"""
@@ -214,3 +216,40 @@ class FileConstraint(Constraint):
raise FileConstraintError("Non-existing file : %s"%value)
return
+class ResourceConstraintError(ConstraintException):
+ pass
+
+class ResourceConstraint(Constraint):
+ """
+ Ensures that the value is looking like a resource name, like
+ <filename>_<GUID>[.<extension>]. We are not validating that this is a
+ valid resource for the reason that the property does not have any notion
+ of tutorial guid.
+
+ TODO : Find a way to properly validate resources by looking them up in the
+ Vault.
+ """
+
+ # Regular expression to parse a resource-like name
+ resource_regexp_text = "(.+)_([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})(\..*)?$"
+ resource_regexp = re.compile(resource_regexp_text)
+
+ def validate(self, value):
+ # TODO : Validate that we will not use an empty resource or if we can
+ # have transitory resource names
+ if value is None:
+ raise ResourceConstraintError("Resource not allowed to have a null value!")
+
+ # Special case : We allow the empty resource name for now
+ if value == "":
+ return value
+
+ # Attempt to see if the value has a resource name inside it
+ match = self.resource_regexp.search(value)
+
+ # If there was no match on the reg exp
+ if not match:
+ raise ResourceConstraintError("Resource name does not seem to be valid : %s" % value)
+
+ # If the name matched, then the value is _PROBABLY_ good
+ return value
diff --git a/tutorius/engine.py b/tutorius/engine.py
index c945e49..10c8d14 100644
--- a/tutorius/engine.py
+++ b/tutorius/engine.py
@@ -7,7 +7,9 @@ from .vault import Vault
from .TProbe import ProbeManager
from .dbustools import save_args
from .tutorial import Tutorial, AutomaticTransitionEvent
+from .translator import ResourceTranslator
+LOGGER = logging.getLogger("sugar.tutorius.engine")
class TutorialRunner(object):
"""
@@ -27,6 +29,7 @@ class TutorialRunner(object):
#Cached objects
self._actions = {}
+ self._installed_actions = {}
#Temp FIX until event/actions have an activity id
self._activity_id = None
@@ -55,15 +58,21 @@ class TutorialRunner(object):
return
#Clear the current actions
- for action in self._actions.values():
- self._pM.uninstall(action)
+ for (action_name, action_address) in self._installed_actions.items():
+ LOGGER.debug("TutorialRunner :: Uninstalling action %s with address %s"%(action_name, action_address))
+ self._pM.uninstall(action_address)
self._actions = {}
+ self._installed_actions = {}
#Clear the EventFilters
for event in self._sEvents:
self._pM.unsubscribe(event)
self._sEvents.clear()
+ def __action_installed(self, action_name, address):
+ LOGGER.debug("TutorialRunner :: Action %s received address %s"%(action_name, address))
+ self._installed_actions[action_name] = address
+
def _setupState(self):
if self._state is None:
raise RuntimeError("Attempting to setupState without a state")
@@ -77,13 +86,15 @@ class TutorialRunner(object):
for (event, next_state) in transitions.values():
if isinstance(event, AutomaticTransitionEvent):
state_name = next_state
- break
+ return state_name
+
+ for (action_name, action) in self._actions.items():
+ self._pM.install(action, save_args(self.__action_installed, action_name), True)
+ LOGGER.debug("TutorialRunner :: Installed action %s"%(action_name))
+ for (event, next_state) in transitions.values():
self._sEvents.add(self._pM.subscribe(event, save_args(self._handleEvent, next_state)))
- for action in self._actions.values():
- self._pM.install(action)
-
return state_name
def enterState(self, state_name):
@@ -112,9 +123,6 @@ class TutorialRunner(object):
# transition in the state definition
self.enterState(self._setupState())
-
-
-
class Engine:
"""
Driver for the execution of tutorials
@@ -137,7 +145,8 @@ class Engine:
if self._tutorial:
self.stop()
- self._tutorial = TutorialRunner(Vault.loadTutorial(tutorialID), self._probeManager)
+ translator_decorator = ResourceTranslator(self._probeManager, tutorialID)
+ self._tutorial = TutorialRunner(Vault.loadTutorial(tutorialID), translator_decorator)
#Get the active activity from the shell
activity = self._shell.get_active_activity()
diff --git a/tutorius/overlayer.py b/tutorius/overlayer.py
index b967739..216d71a 100644
--- a/tutorius/overlayer.py
+++ b/tutorius/overlayer.py
@@ -319,6 +319,198 @@ class TextBubble(gtk.Widget):
gobject.type_register(TextBubble)
+class TextBubbleWImg(gtk.Widget):
+ """
+ A CanvasDrawableWidget drawing a round textbox and a tail pointing
+ to a specified widget.
+ """
+ def __init__(self, text, speaker=None, tailpos=(0,0), imagepath=None):
+ """
+ Creates a new cairo rendered text bubble.
+
+ @param text the text to render in the bubble
+ @param speaker the widget to compute the tail position from
+ @param tailpos (optional) position relative to the bubble to use as
+ the tail position, if no speaker
+ """
+ gtk.Widget.__init__(self)
+
+ # FIXME: ensure previous call does not interfere with widget stacking,
+ # as using a gtk.Layout and stacking widgets may reveal a screwed up
+ # order with the cairo widget on top.
+ self.__label = None
+
+ self.label = text
+ self.speaker = speaker
+ self.tailpos = tailpos
+ self.line_width = 5
+ self.padding = 20
+
+ # image painting
+ self.filename = imagepath
+ self.pixbuf = gtk.gdk.pixbuf_new_from_file(self.filename)
+ self.imgsize = (self.pixbuf.get_width(), self.pixbuf.get_height())
+
+ self._no_expose = False
+ self.__exposer = None
+
+ def draw_with_context(self, context):
+ """
+ Draw using the passed cairo context instead of creating a new cairo
+ context. This eases blending between multiple cairo-rendered
+ widgets.
+ """
+ context.translate(self.allocation.x, self.allocation.y)
+ width = self.allocation.width
+ height = self.allocation.height
+ xradius = width/2
+ yradius = height/2
+ width -= self.line_width
+ height -= self.line_width
+ #
+ # TODO fetch speaker coordinates
+
+ # draw bubble tail if present
+ if self.tailpos != (0,0):
+ context.move_to(xradius-width/4, yradius)
+ context.line_to(self.tailpos[0], self.tailpos[1])
+ context.line_to(xradius+width/4, yradius)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(*xo_line_color)
+ context.stroke_preserve()
+
+ # bubble border
+ context.move_to(width-self.padding, 0.0)
+ context.line_to(self.padding, 0.0)
+ context.arc_negative(self.padding, self.padding, self.padding,
+ 3*pi/2, pi)
+ context.line_to(0.0, height-self.padding)
+ context.arc_negative(self.padding, height-self.padding, self.padding,
+ pi, pi/2)
+ context.line_to(width-self.padding, height)
+ context.arc_negative(width-self.padding, height-self.padding,
+ self.padding, pi/2, 0)
+ context.line_to(width, self.padding)
+ context.arc_negative(width-self.padding, self.padding, self.padding,
+ 0.0, -pi/2)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(*xo_line_color)
+ context.stroke_preserve()
+ context.set_source_rgb(*xo_fill_color)
+ context.fill()
+
+ # bubble painting. Redrawing the inside after the tail will combine
+ if self.tailpos != (0,0):
+ context.move_to(xradius-width/4, yradius)
+ context.line_to(self.tailpos[0], self.tailpos[1])
+ context.line_to(xradius+width/4, yradius)
+ context.set_line_width(self.line_width)
+ context.set_source_rgb(*xo_fill_color)
+ context.fill()
+
+ # draw/write text
+ context.set_source_rgb(1.0, 1.0, 1.0)
+ pangoctx = pangocairo.CairoContext(context)
+ self._text_layout.set_markup(self.__label)
+ text_size = self._text_layout.get_pixel_size()
+ pangoctx.move_to(
+ int((self.allocation.width-text_size[0])/2),
+ int(self.line_width+self.imgsize[1]+self.padding/2))
+ pangoctx.show_layout(self._text_layout)
+
+ # create a new cairo surface to place the image on
+ #surface = cairo.ImageSurface(0,x,y)
+ # create a context to the new surface
+ #ct = cairo.Context(surface)
+
+ # paint image
+ context.set_source_pixbuf(
+ self.pixbuf,
+ int((self.allocation.width-self.imgsize[0])/2),
+ int(self.line_width+self.padding/2))
+
+ context.paint()
+
+ # work done. Be kind to next cairo widgets and reset matrix.
+ context.identity_matrix()
+
+ def do_realize(self):
+ """ Setup gdk window creation. """
+ self.set_flags(gtk.REALIZED | gtk.NO_WINDOW)
+ self.window = self.get_parent_window()
+ if not self._no_expose:
+ self.__exposer = self.connect_after("expose-event", \
+ self.__on_expose)
+
+ def __on_expose(self, widget, event):
+ """Redraw event callback."""
+ ctx = self.window.cairo_create()
+
+ self.draw_with_context(ctx)
+
+ return True
+
+ def _set_label(self, value):
+ """Sets the label and flags the widget to be redrawn."""
+ self.__label = "<b>%s</b>"%value
+ if not self.parent:
+ return
+ ctx = self.parent.window.cairo_create()
+ pangoctx = pangocairo.CairoContext(ctx)
+ self._text_layout = pangoctx.create_layout()
+ self._text_layout.set_markup(value)
+ del pangoctx, ctx#, surf
+
+ def do_size_request(self, requisition):
+ """Fill requisition with size occupied by the widget."""
+ ctx = self.parent.window.cairo_create()
+ pangoctx = pangocairo.CairoContext(ctx)
+ self._text_layout = pangoctx.create_layout()
+ self._text_layout.set_markup(self.__label)
+
+ width, height = self._text_layout.get_pixel_size()
+
+ max_width = width
+ if self.imgsize[0] > width:
+ max_width = self.imgsize[0]
+ requisition.width = int(2*self.padding+max_width)
+
+ requisition.height = int(2*self.padding+height+self.imgsize[1])
+
+ def do_size_allocate(self, allocation):
+ """Save zone allocated to the widget."""
+ self.allocation = allocation
+
+ def _get_label(self):
+ """Getter method for the label property"""
+ return self.__label[3:-4]
+
+ def _set_no_expose(self, value):
+ """setter for no_expose property"""
+ self._no_expose = value
+ if not (self.flags() and gtk.REALIZED):
+ return
+
+ if self.__exposer and value:
+ self.parent.disconnect(self.__exposer)
+ self.__exposer = None
+ elif (not self.__exposer) and (not value):
+ self.__exposer = self.parent.connect_after("expose-event",
+ self.__on_expose)
+
+ def _get_no_expose(self):
+ """getter for no_expose property"""
+ return self._no_expose
+
+ no_expose = property(fset=_set_no_expose, fget=_get_no_expose,
+ doc="Whether the widget should handle exposition events or not.")
+
+ label = property(fget=_get_label, fset=_set_label,
+ doc="Text label which is to be painted on the top of the widget")
+
+gobject.type_register(TextBubbleWImg)
+
+
class Rectangle(gtk.Widget):
"""
A CanvasDrawableWidget drawing a rectangle over a specified widget.
diff --git a/tutorius/properties.py b/tutorius/properties.py
index 5422532..6bd16ee 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -24,7 +24,9 @@ from copy import copy, deepcopy
from .constraints import Constraint, \
UpperLimitConstraint, LowerLimitConstraint, \
MaxSizeConstraint, MinSizeConstraint, \
- ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint
+ ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint, \
+ ResourceConstraint
+
class TPropContainer(object):
"""
@@ -89,6 +91,21 @@ class TPropContainer(object):
except AttributeError:
return object.__setattr__(self, name, value)
+ def replace_property(self, prop_name, new_prop):
+ """
+ Changes the content of a property. This is done in order to support
+ the insertion of executable properties in the place of a portable
+ property. The typical exemple is that a resource property needs to
+ be changed into a file property with the correct file name, since the
+ installation location will be different on every platform.
+
+ @param prop_name The name of the property to be changed
+ @param new_prop The new property to insert
+ @raise AttributeError of the mentionned property doesn't exist
+ """
+ props = object.__getattribute__(self, "_props")
+ props.__setitem__(prop_name, new_prop)
+
def get_properties(self):
"""
Return the list of property names.
@@ -272,6 +289,37 @@ class TFileProperty(TutoriusProperty):
self.default = self.validate(path)
+class TResourceProperty(TutoriusProperty):
+ """
+ Represents a resource in the tutorial. A resource is a file with a specific
+ name that exists under the tutorials folder. It is distributed alongside the
+ tutorial itself.
+
+ When the system encounters a resource, it knows that it refers to a file in
+ the resource folder and that it should translate this resource name to an
+ absolute file name before it is executed.
+
+ E.g. An image is added to a tutorial in an action. On doing so, the creator
+ adds a resource to the tutorial, then saves its name in the resource
+ property of that action. When this tutorial is executed, the Engine
+ replaces all the TResourceProperties inside the action by their equivalent
+ TFileProperties with absolute paths, so that they can be used on any
+ machine.
+ """
+ def __init__(self, resource_name=""):
+ """
+ Creates a new resource pointing to an existing resource.
+
+ @param resource_name The file name of the resource (should be only the
+ file name itself, no directory information)
+ """
+ TutoriusProperty.__init__(self)
+ self.type = "resource"
+
+ self.resource_cons = ResourceConstraint()
+
+ self.default = self.validate("")
+
class TEnumProperty(TutoriusProperty):
"""
Represents a value in a given enumeration. This means that the value will
diff --git a/tutorius/translator.py b/tutorius/translator.py
new file mode 100644
index 0000000..d0504be
--- /dev/null
+++ b/tutorius/translator.py
@@ -0,0 +1,204 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import copy as copy_module
+
+logger = logging.getLogger("ResourceTranslator")
+
+from .properties import *
+from .vault import Vault
+
+class ResourceTranslator(object):
+ """
+ Handles the conversion of resource properties into file
+ properties before action execution. This class works as a decorator
+ to the ProbeManager class, as it is meant to be a transparent layer
+ before sending the action to execution.
+
+ An architectural note : every different type of translation should have its
+ own method (translate_resource, etc...), and this function must be called
+ from the translate method, under the type test. The translate_* method
+ must take in the input property and give the output property that should
+ replace it.
+ """
+
+ def __init__(self, probe_manager, tutorial_id):
+ """
+ Creates a new ResourceTranslator for the given tutorial. This
+ translator is tasked with replacing resource properties of the
+ incoming action into actually usable file properties pointing
+ to the correct resource file. This is done by querying the vault
+ for all the resources and creating a new file property from the
+ returned path.
+
+ @param probe_manager The probe manager to decorate
+ @param tutorial_id The ID of the current tutorial
+ """
+ self._probe_manager = probe_manager
+ self._tutorial_id = tutorial_id
+
+ self._translation_mapping = {}
+
+ def translate_resource(self, res_value):
+ """
+ Replace the TResourceProperty in the container by their
+ runtime-defined file equivalent. Since the resources are stored
+ in a relative manner in the vault and that only the Vault knows
+ which is the current folder for the current tutorial, it needs
+ to transform the resource identifier into the absolute path for
+ the process to be able to use it properly.
+
+ @param res_prop The resource property's value to be translated
+ @return The TFileProperty corresponding to this resource, containing
+ an absolute path to the resource
+ """
+ # We need to replace the resource by a file representation
+ filepath = Vault.get_resource_path(self._tutorial_id, res_value)
+ logger.debug("ResourceTranslator :: Matching resource %s to file %s" % (res_value, filepath))
+
+ # Create the new file representation
+ file_prop = TFileProperty(filepath)
+
+ return file_prop
+
+ def translate(self, prop_container):
+ """
+ Applies the required translation to be able to send the container to an
+ executing endpoint (like a Probe). For each type of property that
+ requires it, there is translation function that will take care of
+ mapping the property to its executable form.
+
+ This function does not return anything, but its post-condition is
+ that all the properties of the input container have been replaced
+ by their corresponding executable versions.
+
+ An example of translation is taking a resource (a relative path to
+ a file under the tutorial folder) and transforming it into a file
+ (a full absolute path) to be able to load it when the activity receives
+ the action.
+
+ @param prop_container The property container in which we want to
+ replace all the resources for file properties and
+ to recursively do so for addon and addonlist
+ properties.
+ """
+ for propname in prop_container.get_properties():
+ prop_value = getattr(prop_container, propname)
+ prop_type = getattr(type(prop_container), propname).type
+
+ # If the property is a resource, then we need to query the
+ # vault to create its correspondent
+ if prop_type == "resource":
+ # Apply the translation
+ file_prop = self.translate_resource(prop_value)
+ # Set the property with the new value
+ prop_container.replace_property(propname, file_prop)
+
+ # If the property is an addon, then its value IS a
+ # container too - we need to translate it
+ elif prop_type == "addon":
+ # Translate the sub properties
+ self.translate(prop_value)
+
+ # If the property is an addon list, then we need to translate all
+ # the elements of the list
+ elif prop_type == "addonlist":
+ # Now, for each sub-container in the list, we will apply the
+ # translation processing. This is done by popping the head of
+ # the list, translating it and inserting it back at the end.
+ for index in range(0, len(prop_value)):
+ # Pop the head of the list
+ container = prop_value[0]
+ del prop_value[0]
+ # Translate the sub-container
+ self.translate(container)
+
+ # Put the value back in the list
+ prop_value.append(container)
+ # Change the list contained in the addonlist property, since
+ # we got a copy of the list when requesting it
+ prop_container.replace_property(propname, prop_value)
+
+ ### ProbeManager interface for decorator ###
+
+ ## Unchanged functions ##
+ def setCurrentActivity(self, activity_id):
+ self._probe_manager.currentActivity = activity_id
+
+ def getCurrentActivity(self):
+ return self._probe_manager.currentActivity
+
+ currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity)
+ def attach(self, activity_id):
+ self._probe_manager.attach(activity_id)
+
+ def detach(self, activity_id):
+ self._probe_manager.detach(activity_id)
+
+ def subscribe(self, event, callback):
+ return self._probe_manager.subscribe(event, callback)
+
+ def unsubscribe(self, address):
+ return self._probe_manager.unsubscribe(address)
+
+ def register_probe(self, process_name, unique_id):
+ self._probe_manager.register_probe(process_name, unique_id)
+
+ def unregister_probe(self, unique_id):
+ self._probe_manager.unregister_probe(unique_id)
+
+ def get_registered_probes_list(self, process_name=None):
+ return self._probe_manager.get_registered_probes_list(process_name)
+
+ ## Decorated functions ##
+ def install(self, action, callback, block=False):
+ # Make a new copy of the action that we want to install,
+ # because translate() changes the action and we
+ # don't want to modify the caller's action representation
+ new_action = copy_module.deepcopy(action)
+ # Execute the replacement
+ self.translate(new_action)
+
+ # Send the new action to the probe manager
+ action_address = self._probe_manager.install(new_action, callback, block)
+
+ # Remember the address
+ self._translation_mapping[action_address] = new_action
+
+ return action_address
+
+ def update(self, action_address, newaction, block=False):
+ # TODO : Repair this as it currently doesn't work.
+ # Actions are being copied, then translated in install(), so they
+ # won't be addressable via the same object that is in the Tutorial
+ # Runner.
+ translated_new_action = copy_module.deepcopy(newaction)
+ self.translate(translated_new_action)
+
+ self._translation_mapping[action_address] = translated_new_action
+
+ return self._probe_manager.update(action_address, translated_new_action, block)
+
+ def uninstall(self, action_address, block=False):
+ return_value = self._probe_manager.uninstall(action_address, block)
+
+ if self._translation_mapping.has_key(action_address):
+ del self._translation_mapping[action_address]
+
+ return return_value
+
diff --git a/tutorius/vault.py b/tutorius/vault.py
index fbe9e75..dc8c434 100644
--- a/tutorius/vault.py
+++ b/tutorius/vault.py
@@ -489,6 +489,7 @@ class Vault(object):
tutorial_path = bundler.get_tutorial_path(tutorial_guid)
# Check if the resource file exists
file_path = os.path.join(tutorial_path, RESOURCES_FOLDER, resource_id)
+ logger.debug("Vault :: Assigning resource %s to file %s "%(resource_id, file_path))
if os.path.isfile(file_path):
return file_path
else: