2 * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
3 * Copyright (C) 2008 Nokia Corporation.
5 * Author: Zeeshan Ali <zeenix@gmail.com>
6 * Ivan Frade <ivan.frade@nokia.com>
8 * This file is part of Rygel.
10 * Rygel is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * Rygel is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 * A container listing a Tracker search result.
31 public class Rygel.Tracker.SearchContainer : Rygel.MediaContainer {
32 /* class-wide constants */
33 private const string TRACKER_SERVICE = "org.freedesktop.Tracker1";
34 private const string RESOURCES_PATH = "/org/freedesktop/Tracker1/Resources";
36 private const string ITEM_VARIABLE = "?item";
37 private const string MODIFIED_PREDICATE = "nfo:fileLastModified";
38 private const string MODIFIED_VARIABLE = "?modified";
39 private const string URL_PREDICATE = "nie:url";
40 private const string URL_VARIABLE = "?url";
42 public SelectionQuery query;
43 public ItemFactory item_factory;
45 private ResourcesIface resources;
47 public SearchContainer (string id,
48 MediaContainer parent,
50 ItemFactory item_factory,
51 QueryTriplets? triplets = null,
52 ArrayList<string>? filters = null) {
53 base (id, parent, title, 0);
55 this.item_factory = item_factory;
57 var variables = new ArrayList<string> ();
58 variables.add (ITEM_VARIABLE);
59 variables.add (URL_VARIABLE);
61 QueryTriplets our_triplets;
62 if (triplets != null) {
63 our_triplets = triplets;
65 our_triplets = new QueryTriplets ();
68 our_triplets.add_triplet (new QueryTriplet (ITEM_VARIABLE,
70 item_factory.category));
71 our_triplets.add_triplet (new QueryTriplet (ITEM_VARIABLE,
74 our_triplets.add_triplet (new QueryTriplet (ITEM_VARIABLE,
78 foreach (var chain in this.item_factory.key_chains) {
79 var variable = ITEM_VARIABLE;
81 foreach (var key in chain) {
82 variable = key + "(" + variable + ")";
85 variables.add (variable);
88 this.query = new SelectionQuery (variables,
94 this.resources = Bus.get_proxy_sync (BusType.SESSION,
98 this.get_children_count.begin ();
99 } catch (IOError error) {
100 critical (_("Failed to connect to session bus: %s"), error.message);
104 public override async MediaObjects? get_children (uint offset,
106 Cancellable? cancellable)
108 var expression = new RelationalExpression ();
109 expression.op = SearchCriteriaOp.EQ;
110 expression.operand1 = "@parentID";
111 expression.operand2 = this.id;
115 return yield this.search (expression,
122 public override async MediaObjects? search (SearchExpression? expression,
125 out uint total_matches,
126 Cancellable? cancellable)
128 var results = new MediaObjects ();
130 if (expression == null || !(expression is RelationalExpression)) {
131 return yield base.search (expression,
138 var query = this.create_query (expression as RelationalExpression,
142 yield query.execute (this.resources);
144 /* Iterate through all items */
145 for (uint i = 0; i < query.result.length[0]; i++) {
146 var id = this.create_child_id_for_urn (query.result[i, 0]);
147 var uri = query.result[i, 1];
148 string[] metadata = this.slice_strvv_tail (query.result, i, 2);
150 var item = this.item_factory.create (id, uri, this, metadata);
155 total_matches = results.size;
160 public override async MediaObject? find_object (string id,
161 Cancellable? cancellable)
163 if (this.is_our_child (id)) {
164 return yield base.find_object (id, cancellable);
170 public string create_child_id_for_urn (string urn) {
171 return this.id + "," + urn;
174 private bool is_our_child (string id) {
175 return id.has_prefix (this.id + ",");
178 private async void get_children_count () {
180 var query = new SelectionQuery.clone (this.query);
182 query.variables = new ArrayList<string> ();
183 query.variables.add ("COUNT(" + ITEM_VARIABLE + ") AS x");
185 yield query.execute (this.resources);
187 this.child_count = query.result[0,0].to_int ();
189 } catch (GLib.Error error) {
190 critical (_("Error getting item count under category '%s': %s"),
191 this.item_factory.category,
198 private SelectionQuery? create_query (RelationalExpression? expression,
201 if (expression.operand1 == "upnp:class" &&
202 !this.item_factory.upnp_class.has_prefix (expression.operand2)) {
206 var query = new SelectionQuery.clone (this.query);
208 if (expression.operand1 == "@parentID") {
209 if (!expression.compare_string (this.id)) {
212 } else if (expression.operand1 != "upnp:class") {
213 var filter = create_filter_for_child (expression);
214 if (filter != null) {
215 query.filters.insert (0, filter);
221 query.offset = offset;
222 query.max_count = max_count;
227 private string? create_filter_for_child (RelationalExpression expression) {
228 string filter = null;
229 string variable = null;
232 if (expression.operand1 == "@id") {
233 variable = ITEM_VARIABLE;
237 var urn = this.get_item_info (expression.operand2, out parent_id);
238 if (urn == null || parent_id == null || parent_id != this.id) {
242 switch (expression.op) {
243 case SearchCriteriaOp.EQ:
244 value = "<" + urn + ">";
246 case SearchCriteriaOp.CONTAINS:
247 value = expression.operand2;
252 if (variable == null || value == null) {
256 switch (expression.op) {
257 case SearchCriteriaOp.EQ:
258 filter = variable + " = " + value;
260 case SearchCriteriaOp.CONTAINS:
261 // We need to escape this twice for Tracker
262 var regex = this.escape_string (Regex.escape_string (value));
264 filter = "regex(" + variable + ", \"" + regex + "\", \"i\")";
271 // Returns the URN and the ID of the parent this item belongs to, or null
272 // if item_id is invalid
273 private string? get_item_info (string item_id,
274 out string parent_id) {
275 var tokens = item_id.split (",", 2);
277 if (tokens[0] != null && tokens[1] != null) {
278 parent_id = tokens[0];
287 * Chops the tail of a particular row in a 2-dimensional string array.
289 * param strvv the 2-dimenstional string array to chop the tail of.
290 * param row the row whose tail needs to be chopped off.
291 * param index index of the first element in the tail.
293 * FIXME: Stop using it once vala supports array slicing syntax for
294 * multi-dimentional arrays.
296 private string[] slice_strvv_tail (string[,] strvv, uint row, uint index) {
297 var slice = new string[strvv.length[1] - index];
299 for (var i = 0; i < slice.length; i++) {
300 slice[i] = strvv[row, i + index];
307 * tracker_sparql_escape_string:
308 * @literal: a string to escape
310 * Escapes a string so that it can be used in a SPARQL query. Copied from
313 * Returns: a newly-allocated string with the escaped version of @literal.
314 * The returned string should be freed with g_free() when no longer needed.
316 private string escape_string (string literal) {
317 StringBuilder str = new StringBuilder ();
321 size_t len = Posix.strcspn ((string) p, "\t\n\r\b\f\"\\");
322 str.append_len ((string) p, (long) len);