1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * brasero
4  * Copyright (C) Rouquier Philippe 2009 <bonfire-app@wanadoo.fr>
5  *
6  * brasero is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * brasero is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include <stdlib.h>
23 
24 #include <libtracker-sparql/tracker-sparql.h>
25 #include <gio/gio.h>
26 
27 #include "brasero-search-tracker.h"
28 #include "brasero-search-engine.h"
29 
30 typedef struct _BraseroSearchTrackerPrivate BraseroSearchTrackerPrivate;
31 struct _BraseroSearchTrackerPrivate
32 {
33 	TrackerSparqlConnection *connection;
34 	GCancellable *cancellable;
35 	GPtrArray *results;
36 
37 	BraseroSearchScope scope;
38 
39 	gchar **mimes;
40 	gchar *keywords;
41 
42 	guint current_call_id;
43 };
44 
45 #define BRASERO_SEARCH_TRACKER_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), BRASERO_TYPE_SEARCH_TRACKER, BraseroSearchTrackerPrivate))
46 
47 static void brasero_search_tracker_init_engine (BraseroSearchEngineIface *iface);
48 
49 G_DEFINE_TYPE_WITH_CODE (BraseroSearchTracker,
50 			 brasero_search_tracker,
51 			 G_TYPE_OBJECT,
52 			 G_IMPLEMENT_INTERFACE (BRASERO_TYPE_SEARCH_ENGINE,
53 					        brasero_search_tracker_init_engine));
54 
55 static gboolean
brasero_search_tracker_is_available(BraseroSearchEngine * engine)56 brasero_search_tracker_is_available (BraseroSearchEngine *engine)
57 {
58 	BraseroSearchTrackerPrivate *priv;
59 
60 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (engine);
61 	return (priv->connection != NULL);
62 }
63 
64 static gint
brasero_search_tracker_num_hits(BraseroSearchEngine * engine)65 brasero_search_tracker_num_hits (BraseroSearchEngine *engine)
66 {
67 	BraseroSearchTrackerPrivate *priv;
68 
69 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (engine);
70 	if (!priv->results)
71 		return 0;
72 
73 	return priv->results->len;
74 }
75 
76 static const gchar *
brasero_search_tracker_uri_from_hit(BraseroSearchEngine * engine,gpointer hit)77 brasero_search_tracker_uri_from_hit (BraseroSearchEngine *engine,
78                                      gpointer hit)
79 {
80 	gchar **tracker_hit;
81 
82 	tracker_hit = hit;
83 
84 	if (!tracker_hit)
85 		return NULL;
86 
87 	if (g_strv_length (tracker_hit) >= 2)
88 		return tracker_hit [1];
89 
90 	return NULL;
91 }
92 
93 static const gchar *
brasero_search_tracker_mime_from_hit(BraseroSearchEngine * engine,gpointer hit)94 brasero_search_tracker_mime_from_hit (BraseroSearchEngine *engine,
95                                       gpointer hit)
96 {
97 	gchar **tracker_hit;
98 
99 	tracker_hit = hit;
100 
101 	if (!tracker_hit)
102 		return NULL;
103 
104 	if (g_strv_length (tracker_hit) >= 3)
105 		return tracker_hit [2];
106 
107 	return NULL;
108 }
109 
110 static int
brasero_search_tracker_score_from_hit(BraseroSearchEngine * engine,gpointer hit)111 brasero_search_tracker_score_from_hit (BraseroSearchEngine *engine,
112                                        gpointer hit)
113 {
114 	gchar **tracker_hit;
115 
116 	tracker_hit = hit;
117 
118 	if (!tracker_hit)
119 		return 0;
120 
121 	if (g_strv_length (tracker_hit) >= 4)
122 		return (int) strtof (tracker_hit [3], NULL);
123 
124 	return 0;
125 }
126 
127 static void brasero_search_tracker_cursor_callback (GObject      *object,
128 						    GAsyncResult *result,
129 						    gpointer      user_data);
130 
131 static void
brasero_search_tracker_cursor_next(BraseroSearchEngine * search,TrackerSparqlCursor * cursor)132 brasero_search_tracker_cursor_next (BraseroSearchEngine *search,
133 				    TrackerSparqlCursor    *cursor)
134 {
135 	BraseroSearchTrackerPrivate *priv;
136 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
137 
138 	tracker_sparql_cursor_next_async (cursor,
139 					  priv->cancellable,
140 					  brasero_search_tracker_cursor_callback,
141 					  search);
142 }
143 
144 static void
brasero_search_tracker_cursor_callback(GObject * object,GAsyncResult * result,gpointer user_data)145 brasero_search_tracker_cursor_callback (GObject      *object,
146 					GAsyncResult *result,
147 					gpointer      user_data)
148 {
149 	BraseroSearchEngine *search;
150 	GError *error = NULL;
151 	TrackerSparqlCursor *cursor;
152 	GList *hits;
153 	gboolean success;
154 
155 	cursor = TRACKER_SPARQL_CURSOR (object);
156 	success = tracker_sparql_cursor_next_finish (cursor, result, &error);
157 
158 	if (error) {
159 		brasero_search_engine_query_error (search, error);
160 		g_error_free (error);
161 
162 		if (cursor) {
163 			g_object_unref (cursor);
164 		}
165 
166 		return;
167 	}
168 
169 	if (!success) {
170 		brasero_search_engine_query_finished (search);
171 
172 		if (cursor) {
173 			g_object_unref (cursor);
174 		}
175 
176 		return;
177 	}
178 
179 	/* We iterate result by result, not n at a time. */
180 	hits = g_list_append (NULL, (gchar*) tracker_sparql_cursor_get_string (cursor, 0, NULL));
181 	brasero_search_engine_hit_added (search, hits);
182 	g_list_free (hits);
183 
184 	/* Get next */
185 	brasero_search_tracker_cursor_next (search, cursor);
186 }
187 
188 static void
brasero_search_tracker_reply(GObject * object,GAsyncResult * result,gpointer user_data)189 brasero_search_tracker_reply (GObject      *object,
190 			      GAsyncResult *result,
191 			      gpointer user_data)
192 {
193 	BraseroSearchEngine *search = BRASERO_SEARCH_ENGINE (user_data);
194 	GError *error = NULL;
195 
196 	TrackerSparqlCursor *cursor;
197 	GList *hits;
198 	gboolean success;
199 
200 	cursor = TRACKER_SPARQL_CURSOR (object);
201 	success = tracker_sparql_cursor_next_finish (cursor, result, &error);
202 
203 	if (cursor) {
204 		g_object_unref (cursor);
205 	}
206 
207 	if (error) {
208 		brasero_search_engine_query_error (search, error);
209 		return;
210 	}
211 
212 	if (!success) {
213 		brasero_search_engine_query_finished (search);
214 
215 		if (cursor) {
216 			g_object_unref (cursor);
217 		}
218 		return;
219 
220 	}
221 
222 	hits = g_list_append (NULL, (gchar*) tracker_sparql_cursor_get_string (cursor, 0, NULL));
223 	brasero_search_engine_hit_added (search, result);
224 	g_list_free (hits);
225 
226 	brasero_search_engine_query_finished (search);
227 }
228 
229 static gboolean
brasero_search_tracker_query_start_real(BraseroSearchEngine * search,gint index)230 brasero_search_tracker_query_start_real (BraseroSearchEngine *search,
231 					 gint index)
232 {
233 	BraseroSearchTrackerPrivate *priv;
234 	gboolean res = FALSE;
235 	GString *query = NULL;
236 
237 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
238 
239 	query = g_string_new ("SELECT ?file ?url ?mime "			/* Which variables should be returned */
240 			      "WHERE {"						/* Start defining the search and its scope */
241 			      "  ?file a nfo:FileDataObject . "			/* File must be a file (not a stream, ...) */
242 	                      "  ?file nie:url ?url . "				/* Get the url of the file */
243 	                      "  ?file nie:mimeType ?mime . "			/* Get its mime */
244 			      "  ?content nie:isStoredAs ?file . ");		/* Get the resource representing the content */
245 
246 	if (priv->mimes) {
247 		int i;
248 
249 		g_string_append (query, " FILTER ( ");
250 		for (i = 0; priv->mimes [i]; i ++) {				/* Filter files according to their mime type */
251 			if (i > 0)
252 				g_string_append (query, " || ");
253 
254 			g_string_append_printf (query,
255 						"?mime = \"%s\"",
256 						priv->mimes [i]);
257 		}
258 		g_string_append (query, " ) ");
259 	}
260 
261 	if (priv->scope) {
262 		gboolean param_added = FALSE;
263 
264 		g_string_append (query,
265 				 "  ?content a ?type . "
266 				 "  FILTER ( ");
267 
268 		if (priv->scope & BRASERO_SEARCH_SCOPE_MUSIC) {
269 			query = g_string_append (query, "?type = nmm:MusicPiece");
270 			param_added = TRUE;
271 		}
272 
273 		if (priv->scope & BRASERO_SEARCH_SCOPE_VIDEO) {
274 			if (param_added)
275 				g_string_append (query, " || ");
276 			query = g_string_append (query, "?type = nfo:Video");
277 
278 			param_added = TRUE;
279 		}
280 
281 		if (priv->scope & BRASERO_SEARCH_SCOPE_PICTURES) {
282 			if (param_added)
283 				g_string_append (query, " || ");
284 			query = g_string_append (query, "?type = nfo:Image");
285 
286 			param_added = TRUE;
287 		}
288 
289 		if (priv->scope & BRASERO_SEARCH_SCOPE_DOCUMENTS) {
290 			if (param_added)
291 				g_string_append (query, " || ");
292 			query = g_string_append (query, "?type = nfo:Document");
293 		}
294 
295 		g_string_append (query,
296 				 " ) ");
297 	}
298 
299 	if (priv->keywords) {
300 		g_string_append_printf (query,
301 					"  ?file fts:match \"%s\" ",		/* File must match possible keywords */
302 					priv->keywords);
303 
304 		g_string_append (query,
305 				 " } "
306 				 "ORDER BY ASC(fts:rank(?file))");
307 	} else {
308 		g_string_append (query,
309 				 "} ORDER BY DESC(?url) DESC(nfo:fileName(?file))");
310 	}
311 
312 	tracker_sparql_connection_query_async (priv->connection,
313 					       query->str,
314 					       priv->cancellable,
315 					       brasero_search_tracker_reply,
316 					       search);
317 	g_string_free (query, TRUE);
318 
319 	return res;
320 }
321 
322 static gboolean
brasero_search_tracker_query_start(BraseroSearchEngine * search)323 brasero_search_tracker_query_start (BraseroSearchEngine *search)
324 {
325 	return brasero_search_tracker_query_start_real (search, 0);
326 }
327 
328 static gboolean
brasero_search_tracker_add_hit_to_tree(BraseroSearchEngine * search,GtkTreeModel * model,gint range_start,gint range_end)329 brasero_search_tracker_add_hit_to_tree (BraseroSearchEngine *search,
330                                         GtkTreeModel *model,
331                                         gint range_start,
332                                         gint range_end)
333 {
334 	BraseroSearchTrackerPrivate *priv;
335 	gint i;
336 
337 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
338 
339 	if (!priv->results)
340 		return FALSE;
341 
342 	for (i = range_start; g_ptr_array_index (priv->results, i) && i < range_end; i ++) {
343 		gchar **hit;
344 		GtkTreeIter row;
345 
346 		hit = g_ptr_array_index (priv->results, i);
347 		gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &row, -1,
348 		                                   BRASERO_SEARCH_TREE_HIT_COL, hit,
349 		                                   -1);
350 	}
351 
352 	return TRUE;
353 }
354 
355 static gboolean
brasero_search_tracker_query_set_scope(BraseroSearchEngine * search,BraseroSearchScope scope)356 brasero_search_tracker_query_set_scope (BraseroSearchEngine *search,
357                                         BraseroSearchScope scope)
358 {
359 	BraseroSearchTrackerPrivate *priv;
360 
361 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
362 	priv->scope = scope;
363 	return TRUE;
364 }
365 
366 static gboolean
brasero_search_tracker_set_query_mime(BraseroSearchEngine * search,const gchar ** mimes)367 brasero_search_tracker_set_query_mime (BraseroSearchEngine *search,
368 				       const gchar **mimes)
369 {
370 	BraseroSearchTrackerPrivate *priv;
371 
372 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
373 
374 	if (priv->mimes) {
375 		g_strfreev (priv->mimes);
376 		priv->mimes = NULL;
377 	}
378 
379 	priv->mimes = g_strdupv ((gchar **) mimes);
380 	return TRUE;
381 }
382 
383 static void
brasero_search_tracker_clean(BraseroSearchTracker * search)384 brasero_search_tracker_clean (BraseroSearchTracker *search)
385 {
386 	BraseroSearchTrackerPrivate *priv;
387 
388 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
389 
390 	if (priv->current_call_id)
391 		g_cancellable_cancel (priv->cancellable);
392 
393 	if (priv->results) {
394 		g_ptr_array_foreach (priv->results, (GFunc) g_strfreev, NULL);
395 		g_ptr_array_free (priv->results, TRUE);
396 		priv->results = NULL;
397 	}
398 
399 	if (priv->keywords) {
400 		g_free (priv->keywords);
401 		priv->keywords = NULL;
402 	}
403 
404 	if (priv->mimes) {
405 		g_strfreev (priv->mimes);
406 		priv->mimes = NULL;
407 	}
408 }
409 
410 static gboolean
brasero_search_tracker_query_new(BraseroSearchEngine * search,const gchar * keywords)411 brasero_search_tracker_query_new (BraseroSearchEngine *search,
412 				  const gchar *keywords)
413 {
414 	BraseroSearchTrackerPrivate *priv;
415 
416 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (search);
417 
418 	brasero_search_tracker_clean (BRASERO_SEARCH_TRACKER (search));
419 	priv->keywords = g_strdup (keywords);
420 
421 	return TRUE;
422 }
423 
424 static void
brasero_search_tracker_init_engine(BraseroSearchEngineIface * iface)425 brasero_search_tracker_init_engine (BraseroSearchEngineIface *iface)
426 {
427 	iface->is_available = brasero_search_tracker_is_available;
428 	iface->query_new = brasero_search_tracker_query_new;
429 	iface->query_set_mime = brasero_search_tracker_set_query_mime;
430 	iface->query_set_scope = brasero_search_tracker_query_set_scope;
431 	iface->query_start = brasero_search_tracker_query_start;
432 
433 	iface->uri_from_hit = brasero_search_tracker_uri_from_hit;
434 	iface->mime_from_hit = brasero_search_tracker_mime_from_hit;
435 	iface->score_from_hit = brasero_search_tracker_score_from_hit;
436 
437 	iface->add_hits = brasero_search_tracker_add_hit_to_tree;
438 	iface->num_hits = brasero_search_tracker_num_hits;
439 }
440 
441 static void
brasero_search_tracker_init(BraseroSearchTracker * object)442 brasero_search_tracker_init (BraseroSearchTracker *object)
443 {
444 	BraseroSearchTrackerPrivate *priv;
445 	GError *error = NULL;
446 
447 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (object);
448 	priv->cancellable = g_cancellable_new ();
449 
450 #ifdef HAVE_TRACKER3
451 	priv->connection = tracker_sparql_connection_bus_new ("org.freedesktop.Tracker3.Miner.Files",
452 							      NULL, NULL, &error);
453 #else
454 	priv->connection = tracker_sparql_connection_get (priv->cancellable, &error);
455 #endif
456 
457 	if (error) {
458 		g_warning ("Could not establish a connection to Tracker: %s", error->message);
459 		g_error_free (error);
460 		g_object_unref (priv->cancellable);
461 
462 		return;
463 	} else if (!priv->connection) {
464 		g_warning ("Could not establish a connection to Tracker, no TrackerSparqlConnection was returned");
465 		g_object_unref (priv->cancellable);
466 
467 		return;
468 	}
469 }
470 
471 static void
brasero_search_tracker_finalize(GObject * object)472 brasero_search_tracker_finalize (GObject *object)
473 {
474 	BraseroSearchTrackerPrivate *priv;
475 
476 	priv = BRASERO_SEARCH_TRACKER_PRIVATE (object);
477 
478 	brasero_search_tracker_clean (BRASERO_SEARCH_TRACKER (object));
479 
480 	if (priv->connection) {
481 		g_object_unref (priv->connection);
482 		priv->connection = NULL;
483 	}
484 
485 	G_OBJECT_CLASS (brasero_search_tracker_parent_class)->finalize (object);
486 }
487 
488 static void
brasero_search_tracker_class_init(BraseroSearchTrackerClass * klass)489 brasero_search_tracker_class_init (BraseroSearchTrackerClass *klass)
490 {
491 	GObjectClass* object_class = G_OBJECT_CLASS (klass);
492 
493 
494 	g_type_class_add_private (klass, sizeof (BraseroSearchTrackerPrivate));
495 
496 	object_class->finalize = brasero_search_tracker_finalize;
497 }
498 
499