From e784a2f0b92ecaf46a77ddca94b31fcc86e0cbae Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 17 Apr 2009 04:36:31 +0000 Subject: Merge branch 'jc_support' into mike Conflicts: source/external/source/sugar-toolkit/src/sugar/tutorius/tests/coretests.py source/external/source/sugar-toolkit/src/sugar/tutorius/tests/run-tests.py --- (limited to 'src/sugar/tutorius/bundler.py') diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py index 585ba88..a28d6ef 100644 --- a/src/sugar/tutorius/bundler.py +++ b/src/sugar/tutorius/bundler.py @@ -21,33 +21,89 @@ This module contains all the data handling class of Tutorius """ import logging -import linecache import os +import uuid +import xml.dom.minidom +import xml.dom.ext from sugar.tutorius import gtkutils, overlayer from sugar.tutorius.core import Tutorial, State, FiniteStateMachine -from sugar.tutorius.actions import DialogMessage, OnceWrapper, BubbleMessage -from sugar.tutorius.filters import GtkWidgetEventFilter, TimerEvent +import sugar.tutorius.actions +import sugar.tutorius.filters +from ConfigParser import SafeConfigParser + +def __get_store_root(): + return os.path.join(os.getenv("SUGAR_PREFIX"),"share","tutorius","data") +def __get_bundle_root(): + return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data") + +INI_ACTIVITY_SECTION = "RELATED_ACTIVITIES" +INI_METADATA_SECTION = "GENERAL_METADATA" +INI_GUID_PROPERTY = "GUID" +INI_NAME_PROPERTY = "NAME" +INI_XML_FSM_PROPERTY = "FSM_FILENAME" class TutorialStore: - def list_avaible_tutorials(self, activity_name): + def list_avaible_tutorials(self, activity_name, activity_vers): """ Recuperate the list of all tutorials present on disk for a given activity. """ - NotImplementedError + + 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) + + 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 + + return tutoGuidName class Serializer: """ - Class that provide serializing and deserializing of the FSM - used in the tutorials to/from disk. + Interface that provide serializing and deserializing of the FSM + used in the tutorials to/from disk. Must be inherited. """ - def save_fsm(self,fsm): + def save_fsm(self,fsm, guid = None): """ - Save fsm to disk. + 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 overwritten. If the GUId is not found, an + exception occur. If no GUID is provided, FSM is written in a new file + in the store root. """ NotImplementedError @@ -60,43 +116,381 @@ class Serializer: class XMLSerializer(Serializer): """ Class that provide serializing and deserializing of the FSM - used in the tutorials to/from a xml file. + used in the tutorials to/from a .xml file. Inherit from Serializer """ - def save_fsm(self,fsm): + def create_state_dict_node(self, state_dict, doc): """ - Save fsm to .xml file + Create and return a xml Node from a State dictionnary. """ - NotImplementedError + statesList = doc.createElement("States") + for state_name, state in state_dict.items(): + stateNode = statesList.appendChild("State") + stateNode.setAttribute("State:Name", state_name) + stateNode = stateNode.appendChild(create_action_list_node(state.action_list, doc)) + stateNode = stateNode.appendChild(create_event_filters_node(state.event_filters, doc)) + return statesList + + def create_action_list_node(self, action_list, doc): + """ + Create and return a xml Node from a Action list. + """ + actionsList = doc.createElement("Actions") + for action in action_list: + actionNode = actionsList.appendChild("Action") + if type(action) is DialogMessage: + actionNode.setAttribute("Action:Class", type(action)) + actionNode.setAttribute("Action:Message", action.message) + actionNode.setAttribute("Action:Position", action.position) + elif type(action) is BubbleMessage: + actionNode.setAttribute("Action:Class", str(type(action))) + actionNode.setAttribute("Action:Message", action.message) + actionNode.setAttribute("Action:Position", action.position) + actionNode.setAttribute("Action:Tail_pos", action.tail_pos) + # TODO : elif for each type of action + elif type(action) is WidgetIdentifyAction: + actionNode.setAttribute("Action:Class", str(type(action))) + # TODO + elif type(action) is ChainAction: + # TODO + actionNode.setAttribute("Action:Class", str(type(action))) + elif type(action) is DisableWidgetAction: + # TODO + actionNode.setAttribute("Action:Class", str(type(action))) + elif type(action) is TypeTextAction: + # TODO + actionNode.setAttribute("Action:Class", str(type(action))) + elif type(action) is ClickAction: + # TODO + actionNode.setAttribute("Action:Class", str(type(action))) + + return actionsList + + def create_event_filters_node(self, event_filters, doc): + """ + Create and return a xml Node from a event filters. + """ + eventFiltersList = doc.createElement("EventFiltersList") + for event_f in event_filters: + eventFilterNode = eventFiltersList.appendChild("EventFilter") + # TODO : elif for each type of event filters + if type(event_f) is TimerEvent: + # TODO + eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f))) + elif type(event_f) is GtkWidgetEventFilter: + # TODO + eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f))) + elif type(event_f) is GtkWidgetTypeFilter: + # TODO + eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f))) + + return eventFiltersList + def save_fsm(self, fsm, xml_filename, path): + """ + Save fsm to disk, in the xml file specified by "xml_filename", in the + "path" folder. If the specified file doesn't exist, it will be created. + """ + doc = xml.dom.minidom.Document() + fsm_element = doc.createElement("FSM") + doc.appendChild(fsm_element) + fsm_element.setAttribute("fsm:Name", fsm.name) + fsm_element.setAttribute("fsm:StartStateName", fsm.start_state_name) + fsm_element = fsm_element.appendChild(create_state_dict_node(fsm.state_dict, doc)) + fsm_element = fsm_element.appendChild(create_action_list_node(fsm.action_list, doc)) + + file_object = open(path + "/" + xml_filename, "w") + xml.dom.ext.PrettyPrint(doc, file_object) + file_object.close() + + + def _find_tutorial_dir_with_guid(self, guid): + """ + Finds the tutorial with the associated GUID. If it is found, return + the path to the tutorial's directory. If it doesn't exist, raise an + IOError. + + A note : if there are two tutorials with this GUID in the folders, + they will both be inspected and the one with the highest version + number will be returned. If they have the same version number, the one + from the global store will be returned. + + @param guid The GUID of the tutorial that is to be loaded. + """ + # Attempt to find the tutorial's directory in the global directory + global_dir = os.path.join(__get_store_root(), guid) + # Then in the activty's bundle path + activity_dir = os.path.join(__get_bundle_root(), guid) + + # If they both exist + if os.path.isdir(global_filename) and os.path.isdir(activity_dir): + # Inspect both metadata files + global_meta = os.path.join(global_dir, "meta.ini") + activity_meta = os.path.join(activity_dir, "meta.ini") + + # Open both config files + global_parser = SafeConfigParser() + global_parser.read(global_meta) + + activity_parser = SafeConfigParser() + activity_parser.read(activity_meta) + + # Get the version number for each tutorial + global_version = global_parser.get(INI_METADATA_SECTION, "version") + activity_version = activity_parser.get(INI_METADATA_SECTION, "version") + + # If the global version is higher or equal, we'll take it + if global_version >= activity_version: + return global_dir + else: + return activity_dir + + # Do we just have the global directory? + if os.path.isdir(global_dir): + return global_dir + + # Or just the activity's bundle directory? + if os.path.isdir(activity_dir): + return activity_dir + + # Error : none of these directories contain the tutorial + raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID"%guid) + + def _load_xml_properties(self, properties_elem): + """ + Changes a list of properties into fully instanciated properties. + + @param properties_elem An XML element reprensenting a list of + properties + """ + return [] + + def _load_xml_event_filters(self, filters_elem): + """ + Loads up a list of Event Filters. + + @param filters_elem An XML Element representing a list of event filters + """ + return [] + + def _load_xml_actions(self, actions_elem): + """ + Transforms an Actions element into a list of instanciated Action. + + @param actions_elem An XML Element representing a list of Actions + """ + return [] + + def _load_xml_states(self, states_elem): + """ + Takes in a States element and fleshes out a complete list of State + objects. + + @param states_elem An XML Element that represents a list of States + """ + return [] + + def _load_xml_fsm(self, fsm_elem): + """ + Takes in an XML element representing an FSM and returns the fully + crafted FSM. + + @param fsm_elem The XML element that describes a FSM + """ + # Load the FSM's name and start state's name + fsm_name = fsm_elem.getAttribute("fsm:Name") + + fsm_start_state_name = None + try: + fsm_start_state_name = fsm_elem.getAttribute("fsm:StartStateName") + except: + pass + + fsm = FiniteStateMachine(fsm_name, start_state_name=fsm_start_state_name) + + # Load the states + states = self._load_xml_states(fsm_elem.getElementsByName("States")) + for state in states: + fsm.add_state(state) + + # Load the actions on this FSM + actions = self._load_xml_actions(fsm_elem.getElementsByName("Actions")) + for action in actions: + fsm.add_action(action) + + # Load the event filters + events = self._load_xml_event_filters(fsm_elem.getElementsByName("EventFiltersList")) + for event in events: + fsm.add_event_filter(event) + + return fsm + def load_fsm(self, guid): """ - Load fsm from xml file who .ini file guid match argument guid. + Load fsm from xml file whose .ini file guid match argument guid. """ - NotImplementedError + # Fetch the directory (if any) + tutorial_dir = self._find_tutorial_dir_with_guid(guid) + + # Open the XML file + tutorial_file = os.path.join(tutorial_dir, "fsm.xml") + + xml_dom = xml.dom.minidom.parse(tutorial_file) + fsm_elem = xml_dom.getElementsByTagName("fsm")[0] + return self._load_xml_fsm(fsm_elem) + + class TutorialBundler: """ This class provide the various data handling methods useable by the tutorial editor. """ - def write_metadata_file(data): + def __init__(self,generated_guid = None): """ - Write metadata to a .ini file. + TODO. + Tutorial_bundler constructor. If a GUID is given in the parameter, the + Tutorial_bundler object will be associated with it. If no GUID is given, + a new GUID will be generated, """ - NotImplementedError - - def write_fsm(fsm): + + self.Guid = generated_guid or uuid.uuid1() + + #Look for the file in the path if a uid is supplied + if generated_guid: + #General store + store_path = os.path.join(__get_store_root(), generated_guid, INI_FILENAME) + if os.path.isfile(store_path): + self.Path = os.path.dirname(store_path) + else: + #Bundle store + bundle_path = os.path.join(__get_bundle_root(), generated_guid, INI_FILENAME) + if os.path.isfile(bundle_path): + self.Path = os.path.dirname(bundle_path) + else: + raise IOError(2,"Unable to locate metadata file for guid '%s'" % generated_guid) + + 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) + 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 fsm to disk. + 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 add_resources(typename, file): + def get_tutorial_path(self): + """ + Return the path of the .ini file associated with the guiven guid set in + the Guid property of the Tutorial_Bundler. If the guid is present in + more than one path, the store_root is given priority. + """ + + store_root = __get_store_root() + bundle_root = __get_bundle_root() + + config = SafeConfigParser() + path = None + + logging.debug("************ Path of store_root folder of activity : " \ + + store_root) + + # iterate in each GUID subfolder + for dir in os.listdir(store_root): + + # iterate for each .ini file in the store_root folder + + for file_name in os.listdir(store_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) == self.Guid: + xml_filename = config.get(INI_METADATA_SECTION, + INI_XML_FSM_PROPERTY) + + path = os.path.join(store_root, self.Guid) + return path + + logging.debug("************ Path of bundle_root folder of activity : " \ + + bundle_root) + + + # iterate in each GUID subfolder + 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): + 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: + path = os.path.join(bundle_root, self.Guid) + return path + + if path is None: + logging.debug("**************** Error : GUID not found") + raise KeyError + + def write_fsm(self, fsm, guid=None): + + """ + 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: + path = get_tutorial_path() + "/meta.ini" + config.read(path) + xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY) + save_fsm(fsm, xml_filename, store_root) + + + def add_resources(self, typename, file): """ Add ressources to metadata. """ - NotImplementedError \ No newline at end of file + raise NotImplementedError("add_resources not implemented") -- cgit v0.9.1