[rygel] media-export: Refactor QueryContainer
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rygel] media-export: Refactor QueryContainer
- Date: Thu, 8 Sep 2011 20:45:33 +0000 (UTC)
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]