Initial commit
[fillmore] / src / marina / ProjectLoader.vala
diff --git a/src/marina/ProjectLoader.vala b/src/marina/ProjectLoader.vala
new file mode 100644 (file)
index 0000000..7a9ffdf
--- /dev/null
@@ -0,0 +1,353 @@
+/* Copyright 2009 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution. 
+ */
+
+using Logging;
+
+namespace Model {
+
+public class LoaderHandler : Object {
+    public signal void load_error(string error_message);
+    public signal void complete();
+    
+    public LoaderHandler() {
+    }
+    
+    public virtual bool commit_library(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+    
+    public virtual bool commit_marina(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+    
+    public virtual bool commit_track(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+    
+    public virtual bool commit_clip(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+    
+    public virtual bool commit_clipfile(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+
+    public virtual bool commit_time_signature_entry(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+
+    public virtual bool commit_tempo_entry(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+
+    public virtual bool commit_click(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+    
+    public virtual bool commit_library_preference(string[] attr_names, string[] attr_values) {
+        return true;
+    }
+    
+    public virtual void leave_library() {
+    }
+
+    public virtual void leave_marina() {
+    }    
+
+    public virtual void leave_track() {
+    }
+    
+    public virtual void leave_clip() {
+    }
+    
+    public virtual void leave_clipfile() {
+        
+    }
+}
+
+// TODO: Move these classes into separate file
+public class XmlTreeLoader {
+    XmlElement current_element = null;
+    public XmlElement root = null;
+
+    public XmlTreeLoader(string document) {
+        MarkupParser parser = { xml_start_element, xml_end_element, null, null };
+        MarkupParseContext context =
+            new MarkupParseContext(parser, (MarkupParseFlags) 0, this, null);
+        try {
+            context.parse(document, document.length);
+        } catch (MarkupError e) {
+        }
+    
+    }    
+    
+    void xml_start_element(GLib.MarkupParseContext c, string name, 
+                           string[] attr_names, string[] attr_values) {
+        Model.XmlElement new_element = new Model.XmlElement(name, attr_names, attr_values, current_element);
+        if (root == null) {
+            root = new_element;
+        } else {
+            assert(current_element != null);
+            current_element.add_child(new_element);
+        }
+        
+        current_element = new_element;
+    }
+
+    void xml_end_element(GLib.MarkupParseContext c, string name) {
+        assert(current_element != null);
+        current_element = current_element.parent;
+    }
+}
+
+// ProjectBuilder is responsible for verifying the structure of the XML document.
+// Subclasses of this class will be responsible for checking the attributes of 
+// any particular element.
+class ProjectBuilder : Object {
+    LoaderHandler handler;
+
+    public signal void error_occurred(string error);
+    
+    public ProjectBuilder(LoaderHandler handler) {
+        this.handler = handler;
+    }
+
+    bool check_name(string expected_name, XmlElement node) {
+        if (node.name == expected_name) {
+            return true;
+        }
+        
+        error_occurred("expected %s, got %s".printf(expected_name, node.name));
+        return false;
+    }
+    
+    void handle_clip(XmlElement clip) {
+        if (check_name("clip", clip)) {
+            if (handler.commit_clip(clip.attribute_names, clip.attribute_values)) {
+                if (clip.children.size != 0) {
+                    error_occurred("clip cannot have children");
+                }
+                handler.leave_clip();
+            }
+        }
+    }
+
+    void handle_track(XmlElement track) {
+        if (check_name("track", track)) {
+            emit(this, Facility.LOADING, Level.VERBOSE, "loading track");
+            if (handler.commit_track(track.attribute_names, track.attribute_values)) {
+                foreach (XmlElement child in track.children) {
+                    handle_clip(child);
+                }
+                handler.leave_track();
+            }
+        }
+    }
+
+    void handle_preference(XmlElement preference) {
+        if ("click" == preference.name) {
+            handler.commit_click(preference.attribute_names, preference.attribute_values);
+        } else if ("library" == preference.name) {
+            handler.commit_library_preference(
+                preference.attribute_names, preference.attribute_values);
+        } else {
+            error_occurred("Unknown preference: %s".printf(preference.name));
+        }
+    }
+    
+    void handle_time_signature(XmlElement time_signature) {
+        foreach (XmlElement child in time_signature.children) {
+            if (check_name("entry", child)) {
+                if (!handler.commit_time_signature_entry(child.attribute_names, 
+                    child.attribute_values)) {
+                        error_occurred("Improper time signature node");
+                }
+            }
+        }
+    }
+    
+    void handle_tempo(XmlElement tempo) {
+        foreach (XmlElement child in tempo.children) {
+            if (check_name("entry", child)) {
+                if (!handler.commit_tempo_entry(child.attribute_names, child.attribute_values)) {
+                    error_occurred("Improper tempo node");
+                }
+            }
+        }
+    }
+    
+    void handle_map(XmlElement map) {
+        switch (map.name) {
+            case "tempo":
+                handle_tempo(map);
+                break;
+            case "time_signature":
+                handle_time_signature(map);
+                break;
+            default:
+                error_occurred("improper map node");
+                break;
+        }
+    }
+    
+    void handle_library(XmlElement library) {
+        if (handler.commit_library(library.attribute_names, library.attribute_values)) {
+            foreach (XmlElement child in library.children) {
+                if (!handler.commit_clipfile(child.attribute_names, child.attribute_values))
+                    error_occurred("Improper library node");
+            } 
+            handler.leave_library();
+        }
+    }
+    
+    void handle_tracks(XmlElement tracks) {
+        foreach (XmlElement child in tracks.children) {
+            handle_track(child);
+        }
+    }
+
+    void handle_preferences(XmlElement preferences) {
+        foreach (XmlElement child in preferences.children) {
+            handle_preference(child);
+        }
+    }
+    void handle_maps(XmlElement maps) {
+        foreach (XmlElement child in maps.children) {
+            handle_map(child);
+        }
+    }
+    public bool check_project(XmlElement? root) {
+        if (root == null) {
+            error_occurred("Invalid XML file!");
+            return false;
+        }
+        
+        if (check_name("marina", root) &&
+            handler.commit_marina(root.attribute_names, root.attribute_values)) {
+            if (root.children.size != 3 && root.children.size != 4) {
+                error_occurred("Improper number of children!");
+                return false;
+            }
+            
+            if (!check_name("library", root.children[0]) ||
+                !check_name("tracks", root.children[1]) ||
+                !check_name("preferences", root.children[2]))
+                return false;
+                
+            if (root.children.size == 4 && !check_name("maps", root.children[3])) {
+                return false;
+            }
+        } else
+            return false;
+        return true;
+    }
+
+    public void build_project(XmlElement? root) {
+        handle_library(root.children[0]);
+        handle_tracks(root.children[1]);
+        handle_preferences(root.children[2]);
+        if (root.children.size == 4) {
+            handle_maps(root.children[3]);
+        }
+        
+        handler.leave_marina();
+    }
+}
+
+public class XmlElement {
+    public string name { get; private set; }
+    
+    public string[] attribute_names;
+    
+    public string[] attribute_values;
+    
+    public Gee.ArrayList<XmlElement> children { get { return _children; } }
+    
+    public weak XmlElement? parent { get; private set; }
+    
+    private Gee.ArrayList<XmlElement> _children;
+    public XmlElement(string name, string[] attribute_names, string[] attribute_values, 
+                        XmlElement? parent) {
+        this.name = name;
+
+        this.attribute_names = copy_array(attribute_names);
+        this.attribute_values = copy_array(attribute_values);
+        this.parent = parent;
+        this._children = new Gee.ArrayList<XmlElement>();
+    }
+    
+    public void add_child(XmlElement child_element) {
+        _children.add(child_element);
+    }
+}
+
+public class ProjectLoader : Object {
+    string? file_name;
+    LoaderHandler loader_handler;
+    string text;
+    size_t text_len;
+    bool project_load_completed = false;
+    bool load_completed_fired = false;
+    bool handler_completed = false;
+
+    public signal void load_started(string filename);
+    public signal void load_complete();
+    public signal void load_error(string error);
+    
+    public ProjectLoader(LoaderHandler loader_handler, string? file_name) {
+        this.file_name = file_name;
+        this.loader_handler = loader_handler;
+        loader_handler.load_error.connect(on_load_error);
+        loader_handler.complete.connect(on_handler_complete);
+    }
+    
+    void on_load_error(string error) {
+        emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error");
+        load_error(error);
+    }
+    
+    void on_handler_complete() {
+        emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_handler_complete");
+        handler_completed = true;
+        if (project_load_completed && !load_completed_fired) {
+            load_completed_fired = true;
+            load_complete();
+        }
+    }
+    
+    public void load() {
+        try {
+            FileUtils.get_contents(file_name, out text, out text_len);
+        } catch (FileError e) {
+            emit(this, Facility.LOADING, Level.MEDIUM, 
+                "error loading %s: %s".printf(file_name, e.message));
+            load_error(e.message);
+            load_complete();
+            return;
+        }
+        emit(this, Facility.LOADING, Level.VERBOSE, "Building tree for %s".printf(file_name));
+        XmlTreeLoader tree_loader = new XmlTreeLoader(text);
+        
+        ProjectBuilder builder = new ProjectBuilder(loader_handler);
+        builder.error_occurred.connect(on_load_error);
+        
+        if (builder.check_project(tree_loader.root)) {
+            emit(this, Facility.LOADING, Level.VERBOSE, "project checked out.  starting load");
+            load_started(file_name);
+            builder.build_project(tree_loader.root);
+            project_load_completed = true;
+            if (handler_completed && !load_completed_fired) {
+                load_completed_fired = true;
+                load_complete();
+            }
+        }
+        else {
+            emit(this, Facility.LOADING, Level.INFO, "project did not check out.  stopping.");
+            load_complete(); 
+        }
+    }
+}
+}