[rygel] media-export: Refactor QueryContainer



commit 211d8301d685f550299b451e0c8814965340f2f5
Author: Jens Georg <mail jensge org>
Date:   Mon Apr 25 12:41:26 2011 +0300

    media-export: Refactor QueryContainer
    
    Split in three classes, a factory, a node container and a leaf
    container. Node containers are based on meta-data and only contain containers
    (either node or leaf) while leaf containers contain real items.

 src/plugins/media-export/Makefile.am               |    3 +
 .../rygel-media-export-leaf-query-container.vala   |   51 +++++
 .../rygel-media-export-node-query-container.vala   |   88 ++++++++
 .../rygel-media-export-object-factory.vala         |    3 +-
 ...rygel-media-export-query-container-factory.vala |  218 ++++++++++++++++++++
 .../rygel-media-export-query-container.vala        |  167 ++--------------
 .../rygel-media-export-root-container.vala         |   28 ++-
 7 files changed, 395 insertions(+), 163 deletions(-)
---
diff --git a/src/plugins/media-export/Makefile.am b/src/plugins/media-export/Makefile.am
index 70e9fb9..451e035 100644
--- a/src/plugins/media-export/Makefile.am
+++ b/src/plugins/media-export/Makefile.am
@@ -25,6 +25,9 @@ librygel_media_export_la_SOURCES = \
 	rygel-media-export-dummy-container.vala \
 	rygel-media-export-root-container.vala \
 	rygel-media-export-query-container.vala \
+	rygel-media-export-query-container-factory.vala \
+	rygel-media-export-node-query-container.vala \
+	rygel-media-export-leaf-query-container.vala \
 	rygel-media-export-dbus-service.vala \
 	rygel-media-export-recursive-file-monitor.vala \
 	rygel-media-export-harvester.vala \
diff --git a/src/plugins/media-export/rygel-media-export-leaf-query-container.vala b/src/plugins/media-export/rygel-media-export-leaf-query-container.vala
new file mode 100644
index 0000000..e82021d
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-leaf-query-container.vala
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 Jens Georg <mail jensge org>.
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+internal class Rygel.MediaExport.LeafQueryContainer : QueryContainer {
+    public LeafQueryContainer (MediaCache       cache,
+                               SearchExpression expression,
+                               string           id,
+                               string           name) {
+        base (cache, expression, id, name);
+    }
+
+    public override async MediaObjects? get_children (uint         offset,
+                                                      uint         max_count,
+                                                      Cancellable? cancellable)
+                                                      throws GLib.Error {
+        uint total_matches;
+        var children = yield this.search (null,
+                                          offset,
+                                          max_count,
+                                          out total_matches,
+                                          cancellable);
+        foreach (var child in children) {
+            child.parent = this;
+        }
+
+        return children;
+    }
+
+    protected override int count_children () throws Error {
+        return (int) this.media_db.get_object_count_by_search_expression
+                                        (this.expression,
+                                         RootContainer.FILESYSTEM_FOLDER_ID);
+    }
+}
diff --git a/src/plugins/media-export/rygel-media-export-node-query-container.vala b/src/plugins/media-export/rygel-media-export-node-query-container.vala
new file mode 100644
index 0000000..152eddd
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-node-query-container.vala
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 Jens Georg <mail jensge org>.
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+internal class Rygel.MediaExport.NodeQueryContainer : QueryContainer {
+    private string template;
+    private string attribute;
+
+    public NodeQueryContainer (MediaCache       cache,
+                               SearchExpression expression,
+                               string           id,
+                               string           name,
+                               string           template,
+                               string           attribute) {
+        base (cache, expression, id, name);
+
+        this.template = template;
+        this.attribute = attribute;
+
+        // base constructor does count_children but it depends on template and
+        // attribute; so we have to call it again here after those two have
+        // been set.
+        try {
+            this.child_count = this.count_children ();
+        } catch (Error error) {};
+    }
+
+    // MediaContainer overrides
+
+    public override async MediaObjects? get_children (uint         offset,
+                                                      uint         max_count,
+                                                      Cancellable? cancellable)
+                                                      throws GLib.Error {
+        var children = new MediaObjects ();
+        var data = this.media_db.get_object_attribute_by_search_expression
+                                        (this.attribute,
+                                         this.expression,
+                                         offset,
+                                         max_count);
+
+        foreach (var meta_data in data) {
+            var new_id = Uri.escape_string (meta_data, "", true);
+            // template contains URL escaped text. This means it might
+            // contain '%' chars which will makes sprintf crash
+            new_id = this.template.replace ("%s", new_id);
+            var factory = QueryContainerFactory.get_default ();
+            var container = factory.create_from_description (this.media_db,
+                                                             new_id,
+                                                             meta_data);
+            container.parent = this;
+            children.add (container);
+        }
+
+        return children;
+    }
+
+    // QueryContainer overrides
+
+    protected override int count_children () throws Error {
+        // Happens during construction
+        if (this.attribute == null || this.expression == null) {
+            return 0;
+        }
+
+        var data = this.media_db.get_object_attribute_by_search_expression
+                                        (this.attribute,
+                                         this.expression,
+                                         0,
+                                         -1);
+        return data.size;
+    }
+}
diff --git a/src/plugins/media-export/rygel-media-export-object-factory.vala b/src/plugins/media-export/rygel-media-export-object-factory.vala
index d1dabc4..d6761f9 100644
--- a/src/plugins/media-export/rygel-media-export-object-factory.vala
+++ b/src/plugins/media-export/rygel-media-export-object-factory.vala
@@ -51,7 +51,8 @@ internal class Rygel.MediaExport.ObjectFactory : Object {
         }
 
         if (id.has_prefix (QueryContainer.PREFIX)) {
-            return new QueryContainer (media_db, id, title);
+            var factory = QueryContainerFactory.get_default ();
+            return factory.create_from_id (media_db, id, title);
         }
 
         if (uri == null) {
diff --git a/src/plugins/media-export/rygel-media-export-query-container-factory.vala b/src/plugins/media-export/rygel-media-export-query-container-factory.vala
new file mode 100644
index 0000000..55d6e65
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-query-container-factory.vala
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2011 Jens Georg <mail jensge org>.
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+using Gee;
+using GUPnP;
+
+internal class Rygel.MediaExport.QueryContainerFactory : Object {
+    // private static members
+    private static QueryContainerFactory instance;
+
+    // private members
+    private HashMap<string,string> virtual_container_map;
+
+    // public static functions
+    public static QueryContainerFactory get_default () {
+        if (unlikely (instance == null)) {
+            instance = new QueryContainerFactory ();
+        }
+
+        return instance;
+    }
+
+    // constructors
+    private QueryContainerFactory () {
+        this.virtual_container_map = new HashMap<string, string> ();
+    }
+
+    // public functions
+
+    /**
+     * Register a plaintext description for a query container. The passed
+     * string will be modified to the checksum id of the container.
+     *
+     * @param id Originally contains the plaintext id which is replaced with
+     *           the hashed id on return.
+     */
+    public void register_id (ref string id) {
+        var md5 = Checksum.compute_for_string (ChecksumType.MD5, id);
+
+        if (!this.virtual_container_map.has_key (md5)) {
+            this.virtual_container_map[md5] = id;
+            debug ("Registering %s for %s", md5, id);
+        }
+
+        id = QueryContainer.PREFIX + md5;
+    }
+
+    /**
+     * Get the plaintext definition from a hashed id.
+     *
+     * Inverse function of register_id().
+     *
+     * @param hash A hashed id
+     * @return the plaintext defintion of the virtual folder
+     */
+    public string? get_virtual_container_definition (string hash) {
+        var id = hash.replace (QueryContainer.PREFIX, "");
+
+        return this.virtual_container_map[id];
+    }
+
+    /**
+     * Factory method.
+     *
+     * Create a QueryContainer directly from MD5 hashed id.
+     *
+     * @param cache An instance of the meta-data cache
+     * @param id    The hashed id of the container
+     * @param name  An the title of the container. If not supplied, it will
+     *              be derived from the plain-text description of the
+     *              container
+     * @return A new instance of QueryContainer
+     */
+    public QueryContainer create_from_id (MediaCache cache,
+                                          string     id,
+                                          string     name = "") {
+        var definition = this.get_virtual_container_definition (id);
+
+        return this.create_from_description (cache, definition, name);
+    }
+
+    /**
+     * Factory method.
+     *
+     * Create a QueryContainer from a plain-text description string.
+     *
+     * @param cache      An instance of the meta-data cache
+     * @param definition Plain-text defintion of the query-container
+     * @param name       The title of the container. If not supplied, it
+     *                   will be derived from the plain-text description of
+     *                   the container
+     * @return A new instance of QueryContainer
+     */
+    public QueryContainer create_from_description (MediaCache cache,
+                                                   string     definition,
+                                                   string     name = "") {
+        var title = name;
+        string attribute = null;
+        string pattern = null;
+        var id = definition;
+
+        this.register_id (ref id);
+
+        var expression = this.parse_description (definition,
+                                                 out pattern,
+                                                 out attribute,
+                                                 ref title);
+
+        if (pattern == null || pattern == "") {
+            return new LeafQueryContainer (cache, expression, id, title);
+        } else {
+            return new NodeQueryContainer (cache,
+                                           expression,
+                                           id,
+                                           title,
+                                           pattern,
+                                           attribute);
+        }
+    }
+
+    // private methods
+
+    /**
+     * Parse a plaintext container description into a search expression.
+     *
+     * Also generates a name for the container and other meta-data necessary
+     * for node containers.
+     *
+     * @param description The plaintext container description
+     * @param pattern     Contains the pattern used for child containers if
+     *                    descrption is for a node container, null otherwise.
+     * @param attribute   Contains the UPnP attribute the container describes
+     *                    if description is for a node container, null
+     *                    otherwise.
+     * @param name        If passed empty, name will be generated from the
+     *                    description.
+     * @return A SearchExpression corresponding to the non-variable part of
+     *         the description.
+     */
+    private SearchExpression parse_description (string     description,
+                                                out string pattern,
+                                                out string attribute,
+                                                ref string name) {
+        var args = description.split (",");
+        var expression = null as SearchExpression;
+        pattern = null;
+        attribute = null;
+
+        int i = 0;
+        while (i < args.length) {
+            if (args[i + 1] != "?") {
+                this.update_search_expression (ref expression,
+                                               args[i],
+                                               args[i + 1]);
+            } else {
+                args[i + 1] = "%s";
+                attribute = args[i].replace (QueryContainer.PREFIX, "");
+                attribute = Uri.unescape_string (attribute);
+                pattern = string.joinv (",", args);
+
+                if (name == "" && i > 0) {
+                    name = Uri.unescape_string (args[i - 1]);
+                }
+
+                break;
+            }
+
+            i += 2;
+        }
+
+        return expression;
+    }
+
+    /**
+     * Update a SearchExpression with a new key = value condition.
+     *
+     * Will modifiy the passed expression to (expression AND (key = value))
+     *
+     * @param expression The expression to update or null to create a new one
+     * @param key        Key of the key/value condition
+     * @param value      Value of the key/value condition
+     */
+    private void update_search_expression (ref SearchExpression? expression,
+                                           string                key,
+                                           string                @value) {
+        var subexpression = new RelationalExpression ();
+        var clean_key = key.replace (QueryContainer.PREFIX, "");
+        subexpression.operand1 = Uri.unescape_string (clean_key);
+        subexpression.op = SearchCriteriaOp.EQ;
+        subexpression.operand2 = Uri.unescape_string (@value);
+
+        if (expression != null) {
+            var conjunction = new LogicalExpression ();
+            conjunction.operand1 = expression;
+            conjunction.operand2 = subexpression;
+            conjunction.op = LogicalOperator.AND;
+            expression = conjunction;
+        } else {
+            expression = subexpression;
+        }
+    }
+}
diff --git a/src/plugins/media-export/rygel-media-export-query-container.vala b/src/plugins/media-export/rygel-media-export-query-container.vala
index 1bf66f9..2e75a8b 100644
--- a/src/plugins/media-export/rygel-media-export-query-container.vala
+++ b/src/plugins/media-export/rygel-media-export-query-container.vala
@@ -21,85 +21,30 @@
 using Gee;
 using GUPnP;
 
-internal class Rygel.MediaExport.QueryContainer : DBContainer {
+internal abstract class Rygel.MediaExport.QueryContainer : DBContainer {
+    // public static members
     public static const string PREFIX = "virtual-container:";
-    private string attribute;
-    private SearchExpression expression;
-    private static HashMap<string,string> virtual_container_map = null;
-    public string plaintext_id;
-    private string pattern = "";
 
-    public QueryContainer (MediaCache media_db,
-                           string     id,
-                           string     name = "") {
-        // parse the id
-        // Following the schema:
-        // virtual-folder:<class>,? -> get all of that class (eg. Albums)
-        // virtual-folder:<class>,<item> -> get all that is contained in that
-        //                                  class
-        // If an item suffix is present, the children are items, otherwise
-        // containers
-        // To define a complete hierarchy of containers, use multiple
-        // levels:
-        // virtual-folder:<meta_data>,?,<meta_data>,? etc.
-        // example: virtual-folder:upnp:album,? -> All albums
-        //          virtual-folder:upnp:album,The White Album -> All tracks of
-        //          the White Album
-        //          virtual-folder:dc:creator,The Beatles,upnp:album,? -> All
-        //          Albums by the Beatles
-        //          the parts not prefixed by virtual-folder: are URL-escaped
-        //          virtual-folder:dc:creator,?,upnp:album,? -> start of
-        //          hierarchy starting with artists then containing albums of
-        //          that artist
-        base (media_db, id, name);
+    // protected members
+    protected SearchExpression expression;
 
-        this.plaintext_id = get_virtual_container_definition (id);
-        debug ("plaintext ID is: %s", this.plaintext_id);
-        var args = this.plaintext_id.split(",");
+    // constructors
+    public QueryContainer (MediaCache       cache,
+                           SearchExpression expression,
+                           string           id,
+                           string           name) {
+        base (cache, id, name);
 
-        if ((args.length % 2) != 0) {
-            assert_not_reached ();
-        }
-
-        int i = 0;
-        while (i < args.length) {
-            if (args[i + 1] != "?") {
-                update_search_expression (args[i], args[i + 1]);
-                if (name == "") {
-                    this.title = Uri.unescape_string (args[i + 1]);
-                }
-            } else {
-                args[i + 1] = "%s";
-                this.attribute = args[i].replace (PREFIX, "");
-                this.attribute = Uri.unescape_string (this.attribute);
-                this.pattern = string.joinv(",", args);
-                break;
-            }
-            i += 2;
-        }
-        this.child_count = this.count_children ();
-    }
+        this.expression = expression;
 
-    private int count_children () {
         try {
-            if (this.pattern == "") {
-                return (int) this.media_db.get_object_count_by_search_expression
-                                        (this.expression,
-                                         RootContainer.FILESYSTEM_FOLDER_ID);
-            } else {
-                var data = this.media_db.get_object_attribute_by_search_expression
-                                        (this.attribute,
-                                         this.expression,
-                                         0,
-                                         -1);
-
-                return data.size;
-            }
-        } catch (Error e) {
-            return 0;
+            this.child_count = this.count_children ();
+        } catch (Error error) {
+            this.child_count = 0;
         }
     }
 
+    // public methods
     public async override MediaObjects? search (SearchExpression? expression,
                                                 uint              offset,
                                                 uint              max_count,
@@ -139,85 +84,5 @@ internal class Rygel.MediaExport.QueryContainer : DBContainer {
         return children;
     }
 
-    public override async MediaObjects? get_children (uint         offset,
-                                                      uint         max_count,
-                                                      Cancellable? cancellable)
-                                                      throws GLib.Error {
-        MediaObjects children;
-
-        if (pattern == "") {
-            // this "duplicates" the search expression but using the same
-            // search expression in a conjunction shouldn't do any harm
-            uint total_matches;
-            children = yield this.search (this.expression,
-                                          offset,
-                                          max_count,
-                                          out total_matches,
-                                          cancellable);
-        } else {
-            children = new MediaObjects ();
-            var data = this.media_db.get_object_attribute_by_search_expression
-                                        (this.attribute,
-                                         this.expression,
-                                         offset,
-                                         max_count);
-            foreach (var meta_data in data) {
-                var new_id = Uri.escape_string (meta_data, "", true);
-                // pattern contains URL escaped text. This means it might
-                // contain '%' chars which will makes sprintf crash
-                new_id = this.pattern.replace ("%s", new_id);
-                register_id (ref new_id);
-                var container = new QueryContainer (this.media_db,
-                                                    new_id,
-                                                    meta_data);
-                children.add (container);
-            }
-        }
-
-        foreach (var child in children) {
-            child.parent = this;
-        }
-
-        return children;
-    }
-
-    public static void register_id (ref string id) {
-        var md5 = Checksum.compute_for_string (ChecksumType.MD5, id);
-        if (virtual_container_map == null) {
-            virtual_container_map = new HashMap<string,string> ();
-        }
-        if (!virtual_container_map.has_key (md5)) {
-            virtual_container_map[md5] = id;
-            debug ("Registering %s for %s", md5, id);
-        }
-
-        id = PREFIX + md5;
-    }
-
-    public static string? get_virtual_container_definition (string hash) {
-        var id = hash.replace(PREFIX, "");
-        if (virtual_container_map != null &&
-            virtual_container_map.has_key (id)) {
-            return virtual_container_map[id];
-        }
-
-        return null;
-    }
-
-    private void update_search_expression (string op1_, string op2) {
-        var exp = new RelationalExpression ();
-        var op1 = op1_.replace (PREFIX, "");
-        exp.operand1 = Uri.unescape_string (op1);
-        exp.op = SearchCriteriaOp.EQ;
-        exp.operand2 = Uri.unescape_string (op2);
-        if (this.expression != null) {
-            var exp2 = new LogicalExpression ();
-            exp2.operand1 = this.expression;
-            exp2.operand2 = exp;
-            exp2.op = LogicalOperator.AND;
-            this.expression = exp2;
-        } else {
-            this.expression = exp;
-        }
-    }
+    protected abstract int count_children () throws Error;
 }
diff --git a/src/plugins/media-export/rygel-media-export-root-container.vala b/src/plugins/media-export/rygel-media-export-root-container.vala
index bdbf683..8e74541 100644
--- a/src/plugins/media-export/rygel-media-export-root-container.vala
+++ b/src/plugins/media-export/rygel-media-export-root-container.vala
@@ -118,7 +118,8 @@ public class Rygel.MediaExport.RootContainer : Rygel.MediaExport.DBContainer {
         var object = yield base.find_object (id, cancellable);
 
         if (object == null && id.has_prefix (QueryContainer.PREFIX)) {
-            var container = new QueryContainer (this.media_db, id);
+            var factory = QueryContainerFactory.get_default ();
+            var container = factory.create_from_id (this.media_db, id);
             container.parent = this;
 
             return container;
@@ -256,9 +257,9 @@ public class Rygel.MediaExport.RootContainer : Rygel.MediaExport.DBContainer {
                     return null;
             }
 
-            QueryContainer.register_id (ref id);
+            var factory = QueryContainerFactory.get_default ();
 
-            return new QueryContainer (this.media_db, id);
+            return factory.create_from_description (this.media_db, id);
         }
 
         return null;
@@ -312,8 +313,11 @@ public class Rygel.MediaExport.RootContainer : Rygel.MediaExport.DBContainer {
             virtual_expression = right_expression;
         }
 
-        var last_argument = query_container.plaintext_id.replace
-                                        (QueryContainer.PREFIX, "");
+        var factory = QueryContainerFactory.get_default ();
+        var plaintext_id = factory.get_virtual_container_definition
+                                        (query_container.id);
+
+        var last_argument = plaintext_id.replace (QueryContainer.PREFIX, "");
 
         var escaped_detail = Uri.escape_string (virtual_expression.operand2,
                                                 "",
@@ -323,8 +327,8 @@ public class Rygel.MediaExport.RootContainer : Rygel.MediaExport.DBContainer {
                                           escaped_detail,
                                           last_argument);
 
-        QueryContainer.register_id (ref new_id);
-        container = new QueryContainer (this.media_db, new_id);
+        container = factory.create_from_description (this.media_db,
+                                                     new_id);
 
         return true;
     }
@@ -428,10 +432,12 @@ public class Rygel.MediaExport.RootContainer : Rygel.MediaExport.DBContainer {
             id = id.slice (0,-1);
         }
 
-        QueryContainer.register_id (ref id);
-        var query_container = new QueryContainer (this.media_db,
-                                                  id,
-                                                  definition.title);
+        var factory = QueryContainerFactory.get_default ();
+        var query_container = factory.create_from_description
+                                        (this.media_db,
+                                         id,
+                                         definition.title);
+
         if (query_container.child_count > 0) {
             query_container.parent = container;
             this.media_db.save_container (query_container);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]