Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/sugar/tutorius/bundler.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/sugar/tutorius/bundler.py')
-rw-r--r--src/sugar/tutorius/bundler.py233
1 files changed, 112 insertions, 121 deletions
diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py
index f9a3911..0eb6b64 100644
--- a/src/sugar/tutorius/bundler.py
+++ b/src/sugar/tutorius/bundler.py
@@ -25,14 +25,16 @@ import os
import uuid
import xml.dom.minidom
-from sugar.tutorius import gtkutils, overlayer
+from sugar.tutorius import gtkutils, overlayer, tutorial
from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
from sugar.tutorius.filters import *
from sugar.tutorius.actions import *
from ConfigParser import SafeConfigParser
+# this is where user installed/generated tutorials will go
def _get_store_root():
- return os.path.join(os.getenv("SUGAR_PREFIX"),"share","tutorius","data")
+ return os.path.join(os.getenv("HOME"),".sugar",os.getenv("SUGAR_PROFILE"),"tutorius","data")
+# this is where activity bundled tutorials should be, under the activity bundle
def _get_bundle_root():
return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data")
@@ -41,62 +43,76 @@ INI_METADATA_SECTION = "GENERAL_METADATA"
INI_GUID_PROPERTY = "GUID"
INI_NAME_PROPERTY = "NAME"
INI_XML_FSM_PROPERTY = "FSM_FILENAME"
+INI_FILENAME = "meta.ini"
+TUTORIAL_FILENAME = "tutorial.xml"
-class TutorialStore:
+class TutorialStore(object):
- def list_avaible_tutorials(self, activity_name, activity_vers):
+ def list_available_tutorials(self, activity_name, activity_vers):
"""
- Recuperate the list of all tutorials present on disk for a
+ Generate the list of all tutorials present on disk for a
given activity.
+
+ @returns a map of tutorial {names : GUID}.
"""
-
- store_root = _get_store_root()
- bundle_root = _get_bundle_root()
-
- logging.debug("*********** Path of store_root : " + store_root)
-
- # Create /data/tutorius if no exists
- if not os.path.exists(store_root):
- os.mkdir(store_root)
- logging.debug("************* Creating %s folder" % store_root)
-
+ # check both under the activity data and user installed folders
+ paths = [_get_store_root(), _get_bundle_root()]
+
tutoGuidName = {}
-
- # iterate in each GUID subfolder
- for dir in os.listdir(store_root):
- # iterate for each ".ini" file in the activity store_root folder
- for file_name in os.listdir(store_root + "/" + dir):
-
- if file_name.endswith(".ini"):
- logging.debug("************** .ini file found : " + file_name)
- # Filter for just .ini files who metadata ACTIVITY_NAME
- # match 'activity_name' given in argument.
- config = SafeConfigParser()
- config.read(file_name)
- # Get all activity tuples (Activity_Name: Activity_Version)
- file_activity_tuples = config.items(INI_ACTIVITY_SECTION)
-
- for i in range(0, len(file_activity_tuples) - 1):
-
- if file_activity_tuples[i][0] == activity_name and \
- int(file_activity_tuples[i][1]) == activity_vers:
- # Add this tutorial guid and name in the dictionary
- file_activity_guid = config.get(INI_METADATA_SECTION,
- INI_GUID_PROPERTY)
- file_activity_name = config.get(INI_METADATA_SECTION,
- INI_NAME_PROPERTY)
- tutoGuidName[file_activity_name] = file_activity_guid
+
+ for repository in paths:
+ # (our) convention dictates that tutorial folders are named
+ # with their GUID (for unicity) but this is not enforced.
+ try:
+ for tuto in os.listdir(repository):
+ parser = SafeConfigParser()
+ parser.read(os.path.join(repository, tuto, INI_FILENAME))
+ guid = parser.get(INI_METADATA_SECTION, INI_GUID_PROPERTY)
+ name = parser.get(INI_METADATA_SECTION, INI_NAME_PROPERTY)
+ activities = parser.options(INI_ACTIVITY_SECTION)
+ # enforce matching activity name AND version, as UI changes
+ # break tutorials. We may lower this requirement when the
+ # UAM gets less dependent on the widget order.
+ # Also note property names are always stored lowercase.
+ if activity_name.lower() in activities:
+ version = parser.get(INI_ACTIVITY_SECTION, activity_name)
+ if activity_vers == version:
+ tutoGuidName[guid] = name
+ except OSError:
+ # the repository may not exist. Continue scanning
+ pass
return tutoGuidName
-
-class Serializer:
+ def load_tutorial(self, Guid):
+ """
+ Rebuilds a tutorial object from it's serialized state.
+ Common storing paths will be scanned.
+
+ @param Guid the generic identifier of the tutorial
+ @returns a Tutorial object containing an FSM
+ """
+ bundle = TutorialBundler(Guid)
+ bundle_path = bundle.get_tutorial_path()
+ config = SafeConfigParser()
+ config.read(os.path.join(bundle_path, INI_FILENAME))
+
+ serializer = XMLSerializer()
+
+ name = config.get(INI_METADATA_SECTION, INI_NAME_PROPERTY)
+ fsm = serializer.load_fsm(Guid)
+
+ tuto = Tutorial(name, fsm)
+ return tuto
+
+
+class Serializer(object):
"""
Interface that provide serializing and deserializing of the FSM
used in the tutorials to/from disk. Must be inherited.
"""
- def save_fsm(self,fsm, guid = None):
+ def save_fsm(self,fsm):
"""
Save fsm to disk. If a GUID parameter is provided, the existing GUID is
located in the .ini files in the store root and bundle root and
@@ -106,7 +122,7 @@ class Serializer:
"""
NotImplementedError
- def load_fsm(self, guid):
+ def load_fsm(self):
"""
Load fsm from disk.
"""
@@ -143,9 +159,7 @@ class XMLSerializer(Serializer):
# Write down just the name of the Action class as the Class
# property --
- # Using .__class__ since type() doesn't have the same behavior
- # with class derivating from object and class that don't
- actionNode.setAttribute("Class", str(action.__class__))
+ actionNode.setAttribute("Class",type(action).__name__)
if type(action) is DialogMessage:
actionNode.setAttribute("Message", action.message.value)
@@ -211,9 +225,7 @@ class XMLSerializer(Serializer):
# Write down just the name of the Action class as the Class
# property --
- # using .__class__ since type() doesn't have the same behavior
- # with class derivating from object and class that don't
- eventFilterNode.setAttribute("Class", str(event_f.__class__))
+ eventFilterNode.setAttribute("Class", type(event_f).__name__)
# Write the name of the next state
eventFilterNode.setAttribute("NextState", event_f.next_state)
@@ -365,38 +377,38 @@ class XMLSerializer(Serializer):
description
"""
# TO ADD: an elif for each type of action
- if action.getAttribute("Class") == str(DialogMessage):
+ if action.getAttribute("Class") == 'DialogMessage':
message = action.getAttribute("Message")
positionX = int(action.getAttribute("PositionX"))
positionY = int(action.getAttribute("PositionY"))
position = [positionX, positionY]
return DialogMessage(message,position)
- elif action.getAttribute("Class") == str(BubbleMessage):
+ elif action.getAttribute("Class") == 'BubbleMessage':
message = action.getAttribute("Message")
positionX = int(action.getAttribute("PositionX"))
positionY = int(action.getAttribute("PositionY"))
position = [positionX, positionY]
- tail_posX = action.getAttribute("Tail_posX")
- tail_posY = action.getAttribute("Tail_posY")
+ tail_posX = int(action.getAttribute("Tail_posX"))
+ tail_posY = int(action.getAttribute("Tail_posY"))
tail_pos = [tail_posX, tail_posY]
return BubbleMessage(message,position,None,tail_pos)
- elif action.getAttribute("Class") == str(WidgetIdentifyAction):
+ elif action.getAttribute("Class") == 'WidgetIdentifyAction':
return WidgetIdentifyAction()
- elif action.getAttribute("Class") == str(ChainAction):
+ elif action.getAttribute("Class") == 'ChainAction':
# Load the subactions
subActionsList = self._load_xml_actions(action.getElementsByTagName("Actions")[0])
return ChainAction(subActionsList)
- elif action.getAttribute("Class") == str(DisableWidgetAction):
+ elif action.getAttribute("Class") == 'DisableWidgetAction':
# Get the target
targetName = action.getAttribute("Target")
return DisableWidgetAction(targetName)
- elif action.getAttribute("Class") == str(TypeTextAction):
+ elif action.getAttribute("Class") == 'TypeTextAction':
# Get the widget and the text to type
widget = action.getAttribute("Widget")
text = action.getAttribute("Text")
return TypeTextAction(widget, text)
- elif action.getAttribute("Class") == str(ClickAction):
+ elif action.getAttribute("Class") == 'ClickAction':
# Load the widget to click
widget = action.getAttribute("Widget")
@@ -484,7 +496,7 @@ class XMLSerializer(Serializer):
tutorial_dir = self._find_tutorial_dir_with_guid(guid)
# Open the XML file
- tutorial_file = os.path.join(tutorial_dir, "fsm.xml")
+ tutorial_file = os.path.join(tutorial_dir, TUTORIAL_FILENAME)
xml_dom = xml.dom.minidom.parse(tutorial_file)
@@ -493,7 +505,7 @@ class XMLSerializer(Serializer):
return self._load_xml_fsm(fsm_elem)
-class TutorialBundler:
+class TutorialBundler(object):
"""
This class provide the various data handling methods useable by the tutorial
editor.
@@ -506,7 +518,7 @@ class TutorialBundler:
a new GUID will be generated,
"""
- self.Guid = generated_guid or uuid.uuid1()
+ self.Guid = generated_guid or str(uuid.uuid1())
#Look for the file in the path if a uid is supplied
if generated_guid:
@@ -524,48 +536,28 @@ class TutorialBundler:
else:
#Create the folder, any failure will go through to the caller for now
- store_path = os.path.join(_get_store_root(), generated_guid)
- os.mkdir(store_path)
+ store_path = os.path.join(_get_store_root(), self.Guid)
+ os.makedirs(store_path)
self.Path = store_path
-
- def __SetGuid(self, value):
- self.__guid = value
-
- def __GetGuid(self):
- return self.__guid
-
- def __DelGuid(self):
- del self.__guid
-
- def __SetPath(self, value):
- self.__path = value
-
- def __GetPath(self):
- return self.__path
-
- def __DelPath(self):
- del self.__path
-
- Guid = property(fget=__SetGuid,
- fset=__GetGuid,
- fdel=__DelGuid,
- doc="The guid associated with the Tutoria_Bundler")
-
- Path = property(fget=__SetPath,
- fset=__GetPath,
- fdel=__DelPath,
- doc="The path associated with the Tutoria_Bundler")
-
-
- def write_metadata_file(self, data):
- """
- Write metadata to a property file. If a GUID is provided, TutorialBundler
- will try to find and overwrite the existing property file who contain the
- given GUID, and will raise an exception if it cannot find it.
- """
- NotImplementedError
-
+ def write_metadata_file(self, tutorial):
+ """
+ Write metadata to the property file.
+ @param tutorial Tutorial for which to write metadata
+ """
+ #Create the Config Object and populate it
+ cfg = SafeConfigParser()
+ cfg.add_section(INI_METADATA_SECTION)
+ cfg.set(INI_METADATA_SECTION, INI_GUID_PROPERTY, self.Guid)
+ cfg.set(INI_METADATA_SECTION, INI_NAME_PROPERTY, tutorial.name)
+ cfg.set(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY, TUTORIAL_FILENAME)
+ cfg.add_section(INI_ACTIVITY_SECTION)
+ cfg.set(INI_ACTIVITY_SECTION, os.environ['SUGAR_BUNDLE_NAME'],
+ os.environ['SUGAR_BUNDLE_VERSION'])
+
+ #Write the ini file
+ cfg.write( file( os.path.join(self.Path, INI_FILENAME), 'w' ) )
+
def get_tutorial_path(self):
"""
Return the path of the .ini file associated with the guiven guid set in
@@ -587,16 +579,16 @@ class TutorialBundler:
# iterate for each .ini file in the store_root folder
- for file_name in os.listdir(store_root + "/" + dir):
+ for file_name in os.listdir(os.path.join(store_root, dir)):
if file_name.endswith(".ini"):
logging.debug("******************* Found .ini file : " \
+ file_name)
- config.read(file_name)
+ config.read(os.path.join(store_root, dir, file_name))
if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid:
xml_filename = config.get(INI_METADATA_SECTION,
INI_XML_FSM_PROPERTY)
- path = os.path.join(store_root, self.Guid)
+ path = os.path.join(store_root, dir)
return path
logging.debug("************ Path of bundle_root folder of activity : " \
@@ -607,12 +599,12 @@ class TutorialBundler:
for dir in os.listdir(bundle_root):
# iterate for each .ini file in the bundle_root folder
- for file_name in os.listdir(bundle_root + "/" + dir):
+ for file_name in os.listdir(os.path.join(bundle_root, dir)):
if file_name.endswith(".ini"):
logging.debug("******************* Found .ini file : " \
+ file_name)
- config.read(file_name)
- if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == guid:
+ config.read(os.path.join(bundle_root, dir, file_name))
+ if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid:
path = os.path.join(bundle_root, self.Guid)
return path
@@ -620,25 +612,24 @@ class TutorialBundler:
logging.debug("**************** Error : GUID not found")
raise KeyError
- def write_fsm(self, fsm, guid=None):
+ def write_fsm(self, fsm):
"""
Save fsm to disk. If a GUID parameter is provided, the existing GUID is
located in the .ini files in the store root and bundle root and
the corresponding FSM is/are created or overwritten. If the GUID is not
found, an exception occur.
- """
-
+ """
+
config = SafeConfigParser()
-
- if guid is not None:
- serializer = XMLSerializer()
- path = get_tutorial_path() + "/meta.ini"
- config.read(path)
- xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY)
- serializer.save_fsm(fsm, xml_filename, store_root)
-
-
+
+ serializer = XMLSerializer()
+ path = os.path.join(self.Path, "meta.ini")
+ config.read(path)
+ xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY)
+ serializer.save_fsm(fsm, xml_filename, self.Path)
+
+
def add_resources(self, typename, file):
"""
Add ressources to metadata.