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