1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * Copyright (C) 2005 Mr Jamie McCracken
4  *
5  * Caja is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * Caja is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; see the file COPYING.  If not,
17  * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Jamie McCracken <jamiemcc@gnome.org>
21  *
22  */
23 
24 #include <config.h>
25 #include <gmodule.h>
26 #include <string.h>
27 
28 #include <eel/eel-gtk-macros.h>
29 
30 #include "caja-search-engine-tracker.h"
31 
32 typedef struct _TrackerClient TrackerClient;
33 
34 typedef enum
35 {
36     TRACKER_0_6 = 1 << 0,
37     TRACKER_0_7 = 1 << 1,
38     TRACKER_0_8 = 1 << 2
39 } TrackerVersion;
40 
41 
42 /* tracker 0.6 API */
43 typedef void (*TrackerArrayReply) (char **result, GError *error, gpointer user_data);
44 
45 static TrackerClient *	(*tracker_connect)		(gboolean enable_warnings,
46         gint     timeout) = NULL;
47 static void		(*tracker_disconnect)		(TrackerClient *client) = NULL;
48 static void		(*tracker_cancel_last_call)	(TrackerClient *client) = NULL;
49 static int		(*tracker_get_version)		(TrackerClient *client, GError **error) = NULL;
50 
51 
52 static void (*tracker_search_metadata_by_text_async) (TrackerClient *client,
53         const char *query,
54         TrackerArrayReply callback,
55         gpointer user_data) = NULL;
56 static void (*tracker_search_metadata_by_text_and_mime_async) (TrackerClient *client,
57         const char *query,
58         const char **mimes,
59         TrackerArrayReply callback,
60         gpointer user_data) = NULL;
61 static void (*tracker_search_metadata_by_text_and_location_async) (TrackerClient *client,
62         const char *query,
63         const char *location,
64         TrackerArrayReply callback,
65         gpointer user_data) = NULL;
66 static void (*tracker_search_metadata_by_text_and_mime_and_location_async) (TrackerClient *client,
67         const char *query,
68         const char **mimes,
69         const char *location,
70         TrackerArrayReply callback,
71         gpointer user_data) = NULL;
72 
73 
74 /* tracker 0.8 API */
75 typedef enum
76 {
77     TRACKER_CLIENT_ENABLE_WARNINGS = 1 << 0
78 } TrackerClientFlags;
79 
80 typedef void (*TrackerReplyGPtrArray) (GPtrArray *result,
81                                        GError    *error,
82                                        gpointer   user_data);
83 
84 static TrackerClient *	(*tracker_client_new)			(TrackerClientFlags      flags,
85         gint                    timeout) = NULL;
86 static gchar *		(*tracker_sparql_escape)		(const gchar            *str) = NULL;
87 static guint		(*tracker_resources_sparql_query_async)	(TrackerClient          *client,
88         const gchar            *query,
89         TrackerReplyGPtrArray   callback,
90         gpointer                user_data) = NULL;
91 
92 
93 static struct TrackerDlMapping
94 {
95     const char	*fn_name;
96     gpointer	*fn_ptr_ref;
97     TrackerVersion	versions;
98 } tracker_dl_mapping[] =
99 {
100 #define MAP(a,v) { #a, (gpointer *)&a, v }
101     MAP (tracker_connect, TRACKER_0_6 | TRACKER_0_7),
102     MAP (tracker_disconnect, TRACKER_0_6 | TRACKER_0_7),
103     MAP (tracker_get_version, TRACKER_0_6),
104     MAP (tracker_cancel_last_call, TRACKER_0_6 | TRACKER_0_7 | TRACKER_0_8),
105     MAP (tracker_search_metadata_by_text_async, TRACKER_0_6 | TRACKER_0_7),
106     MAP (tracker_search_metadata_by_text_and_location_async, TRACKER_0_6 | TRACKER_0_7),
107     MAP (tracker_client_new, TRACKER_0_8),
108     MAP (tracker_sparql_escape, TRACKER_0_8),
109     MAP (tracker_resources_sparql_query_async, TRACKER_0_8)
110 #undef MAP
111 };
112 
113 
114 static TrackerVersion
open_libtracker(void)115 open_libtracker (void)
116 {
117     static gboolean done = FALSE;
118     static TrackerVersion version = 0;
119     gpointer x;
120 
121     if (!done)
122     {
123         int i;
124         GModule *tracker;
125         GModuleFlags flags;
126 
127         done = TRUE;
128         flags = G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL;
129 
130         tracker = g_module_open ("libtracker-client-0.8.so.0", flags);
131         version = TRACKER_0_8;
132 
133         if (!tracker)
134         {
135             tracker = g_module_open ("libtracker-client-0.7.so.0", flags);
136 
137             if (tracker && !g_module_symbol (tracker, "tracker_resources_sparql_query_async", &x))
138             {
139                 version = TRACKER_0_7;
140             }
141         }
142 
143         if (!tracker)
144         {
145             tracker = g_module_open ("libtrackerclient.so.0", flags);
146             version = TRACKER_0_6;
147         }
148 
149         if (!tracker)
150         {
151             tracker = g_module_open ("libtracker.so.0", flags);
152             version = TRACKER_0_6;
153         }
154 
155         if (!tracker)
156             return 0;
157 
158         for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
159         {
160             if ((tracker_dl_mapping[i].versions & version) == 0)
161                 continue;
162 
163             if (!g_module_symbol (tracker, tracker_dl_mapping[i].fn_name,
164                                   tracker_dl_mapping[i].fn_ptr_ref))
165             {
166                 g_warning ("Missing symbol '%s' in libtracker\n",
167                            tracker_dl_mapping[i].fn_name);
168                 g_module_close (tracker);
169 
170                 for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
171                     tracker_dl_mapping[i].fn_ptr_ref = NULL;
172 
173                 return 0;
174             }
175         }
176     }
177 
178     return version;
179 }
180 
181 
182 struct CajaSearchEngineTrackerDetails
183 {
184     CajaQuery 	*query;
185     TrackerClient 	*client;
186     gboolean 	query_pending;
187     TrackerVersion	version;
188 };
189 
190 G_DEFINE_TYPE (CajaSearchEngineTracker,
191                caja_search_engine_tracker,
192                CAJA_TYPE_SEARCH_ENGINE);
193 
194 static CajaSearchEngineClass *parent_class = NULL;
195 
196 static void
finalize(GObject * object)197 finalize (GObject *object)
198 {
199     CajaSearchEngineTracker *tracker;
200 
201     tracker = CAJA_SEARCH_ENGINE_TRACKER (object);
202 
203     if (tracker->details->query)
204     {
205         g_object_unref (tracker->details->query);
206         tracker->details->query = NULL;
207     }
208 
209     if (tracker->details->version == TRACKER_0_8)
210     {
211         g_object_unref (tracker->details->client);
212     }
213     else
214     {
215         tracker_disconnect (tracker->details->client);
216     }
217 
218     g_free (tracker->details);
219 
220     EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
221 }
222 
223 
224 /* stolen from tracker sources, tracker.c */
225 static void
sparql_append_string_literal(GString * sparql,const gchar * str)226 sparql_append_string_literal (GString     *sparql,
227                               const gchar *str)
228 {
229     char *s;
230 
231     s = tracker_sparql_escape (str);
232 
233     g_string_append_c (sparql, '"');
234     g_string_append (sparql, s);
235     g_string_append_c (sparql, '"');
236 
237     g_free (s);
238 }
239 
240 
241 static void
search_callback(gpointer results,GError * error,gpointer user_data)242 search_callback (gpointer results, GError *error, gpointer user_data)
243 {
244     CajaSearchEngineTracker *tracker;
245     GList *hit_uris;
246     gint i;
247     char *uri;
248 
249     tracker = CAJA_SEARCH_ENGINE_TRACKER (user_data);
250     hit_uris = NULL;
251 
252     tracker->details->query_pending = FALSE;
253 
254     if (error)
255     {
256         caja_search_engine_error (CAJA_SEARCH_ENGINE (tracker), error->message);
257         g_error_free (error);
258         return;
259     }
260 
261     if (! results)
262     {
263         return;
264     }
265 
266     if (tracker->details->version == TRACKER_0_8)
267     {
268         GPtrArray *OUT_result;
269 
270         /* new tracker 0.8 API */
271         OUT_result = (GPtrArray*) results;
272 
273         for (i = 0; i < OUT_result->len; i++)
274         {
275             uri = g_strdup (((gchar **) OUT_result->pdata[i])[0]);
276             if (uri)
277             {
278                 hit_uris = g_list_prepend (hit_uris, (char *)uri);
279             }
280         }
281 
282         g_ptr_array_foreach (OUT_result, (GFunc) g_free, NULL);
283         g_ptr_array_free (OUT_result, TRUE);
284 
285     }
286     else
287     {
288         char **results_p;
289 
290         /* old tracker 0.6 API */
291         for (results_p = results; *results_p; results_p++)
292         {
293             if (tracker->details->version == TRACKER_0_6)
294                 uri = g_filename_to_uri (*results_p, NULL, NULL);
295             else
296                 uri = g_strdup (*results_p);
297 
298             if (uri)
299             {
300                 hit_uris = g_list_prepend (hit_uris, (char *)uri);
301             }
302         }
303         g_strfreev ((gchar **)results);
304     }
305 
306     caja_search_engine_hits_added (CAJA_SEARCH_ENGINE (tracker), hit_uris);
307     caja_search_engine_finished (CAJA_SEARCH_ENGINE (tracker));
308     g_list_free_full (hit_uris, g_free);
309 }
310 
311 
312 static void
caja_search_engine_tracker_start(CajaSearchEngine * engine)313 caja_search_engine_tracker_start (CajaSearchEngine *engine)
314 {
315     CajaSearchEngineTracker *tracker;
316     GList 	*mimetypes, *l;
317     char 	*search_text, *location, *location_uri;
318     char 	**mimes;
319     int 	i, mime_count;
320     GString *sparql;
321 
322     tracker = CAJA_SEARCH_ENGINE_TRACKER (engine);
323 
324 
325     if (tracker->details->query_pending)
326     {
327         return;
328     }
329 
330     if (tracker->details->query == NULL)
331     {
332         return;
333     }
334 
335     search_text = caja_query_get_text (tracker->details->query);
336 
337     mimetypes = caja_query_get_mime_types (tracker->details->query);
338 
339     location_uri = caja_query_get_location (tracker->details->query);
340 
341     if (location_uri)
342     {
343         location = (tracker->details->version == TRACKER_0_6) ?
344                    g_filename_from_uri (location_uri, NULL, NULL) :
345                    g_strdup (location_uri);
346         g_free (location_uri);
347     }
348     else
349     {
350         location = NULL;
351     }
352 
353     mime_count = g_list_length (mimetypes);
354 
355     i = 0;
356     sparql = NULL;
357 
358     if (tracker->details->version == TRACKER_0_8)
359     {
360         /* new tracker 0.8 API */
361         sparql = g_string_new ("SELECT ?url WHERE { ?file a nfo:FileDataObject ; nie:url ?url; ");
362         if (mime_count > 0)
363             g_string_append (sparql, "nie:mimeType ?mime ; ");
364         g_string_append (sparql, "fts:match ");
365         sparql_append_string_literal (sparql, search_text);
366 
367         if (location || mime_count > 0)
368         {
369             g_string_append (sparql, " . FILTER (");
370 
371             if (location)
372             {
373                 g_string_append (sparql, "fn:starts-with(?url, ");
374                 sparql_append_string_literal (sparql, location);
375                 g_string_append (sparql, ")");
376             }
377 
378             if (mime_count > 0)
379             {
380                 if (location)
381                     g_string_append (sparql, " && ");
382                 g_string_append (sparql, "(");
383                 for (l = mimetypes; l != NULL; l = l->next)
384                 {
385                     if (l != mimetypes)
386                         g_string_append (sparql, " || ");
387                     g_string_append (sparql, "?mime = ");
388                     sparql_append_string_literal (sparql, l->data);
389                 }
390                 g_string_append (sparql, ")");
391             }
392 
393             g_string_append (sparql, ")");
394         }
395         g_string_append (sparql, " }");
396 
397         tracker_resources_sparql_query_async (tracker->details->client,
398                                               sparql->str,
399                                               (TrackerReplyGPtrArray) search_callback,
400                                               tracker);
401         g_string_free (sparql, TRUE);
402 
403     }
404     else
405     {
406         /* old tracker 0.6 API */
407         if (mime_count > 0)
408         {
409             /* convert list into array */
410             mimes = g_new (char *, (mime_count + 1));
411 
412             for (l = mimetypes; l != NULL; l = l->next)
413             {
414                 mimes[i] = g_strdup (l->data);
415                 i++;
416             }
417 
418             mimes[mime_count] = NULL;
419 
420             if (location)
421             {
422                 tracker_search_metadata_by_text_and_mime_and_location_async (tracker->details->client,
423                         search_text, (const char **)mimes, location,
424                         (TrackerArrayReply) search_callback,
425                         tracker);
426             }
427             else
428             {
429                 tracker_search_metadata_by_text_and_mime_async (tracker->details->client,
430                         search_text, (const char**)mimes,
431                         (TrackerArrayReply) search_callback,
432                         tracker);
433             }
434 
435             g_strfreev (mimes);
436 
437         }
438         else
439         {
440             if (location)
441             {
442                 tracker_search_metadata_by_text_and_location_async (tracker->details->client,
443                         search_text,
444                         location,
445                         (TrackerArrayReply) search_callback,
446                         tracker);
447             }
448             else
449             {
450                 tracker_search_metadata_by_text_async (tracker->details->client,
451                                                        search_text,
452                                                        (TrackerArrayReply) search_callback,
453                                                        tracker);
454             }
455         }
456     }
457 
458     g_free (location);
459 
460     tracker->details->query_pending = TRUE;
461     g_free (search_text);
462     g_list_free_full (mimetypes, g_free);
463 }
464 
465 static void
caja_search_engine_tracker_stop(CajaSearchEngine * engine)466 caja_search_engine_tracker_stop (CajaSearchEngine *engine)
467 {
468     CajaSearchEngineTracker *tracker;
469 
470     tracker = CAJA_SEARCH_ENGINE_TRACKER (engine);
471 
472     if (tracker->details->query && tracker->details->query_pending)
473     {
474         tracker_cancel_last_call (tracker->details->client);
475         tracker->details->query_pending = FALSE;
476     }
477 }
478 
479 static gboolean
caja_search_engine_tracker_is_indexed(CajaSearchEngine * engine)480 caja_search_engine_tracker_is_indexed (CajaSearchEngine *engine)
481 {
482     return TRUE;
483 }
484 
485 static void
caja_search_engine_tracker_set_query(CajaSearchEngine * engine,CajaQuery * query)486 caja_search_engine_tracker_set_query (CajaSearchEngine *engine, CajaQuery *query)
487 {
488     CajaSearchEngineTracker *tracker;
489 
490     tracker = CAJA_SEARCH_ENGINE_TRACKER (engine);
491 
492     if (query)
493     {
494         g_object_ref (query);
495     }
496 
497     if (tracker->details->query)
498     {
499         g_object_unref (tracker->details->query);
500     }
501 
502     tracker->details->query = query;
503 }
504 
505 static void
caja_search_engine_tracker_class_init(CajaSearchEngineTrackerClass * class)506 caja_search_engine_tracker_class_init (CajaSearchEngineTrackerClass *class)
507 {
508     GObjectClass *gobject_class;
509     CajaSearchEngineClass *engine_class;
510 
511     parent_class = g_type_class_peek_parent (class);
512 
513     gobject_class = G_OBJECT_CLASS (class);
514     gobject_class->finalize = finalize;
515 
516     engine_class = CAJA_SEARCH_ENGINE_CLASS (class);
517     engine_class->set_query = caja_search_engine_tracker_set_query;
518     engine_class->start = caja_search_engine_tracker_start;
519     engine_class->stop = caja_search_engine_tracker_stop;
520     engine_class->is_indexed = caja_search_engine_tracker_is_indexed;
521 }
522 
523 static void
caja_search_engine_tracker_init(CajaSearchEngineTracker * engine)524 caja_search_engine_tracker_init (CajaSearchEngineTracker *engine)
525 {
526     engine->details = g_new0 (CajaSearchEngineTrackerDetails, 1);
527 }
528 
529 
530 CajaSearchEngine *
caja_search_engine_tracker_new(void)531 caja_search_engine_tracker_new (void)
532 {
533     CajaSearchEngineTracker *engine;
534     TrackerClient *tracker_client;
535     TrackerVersion version;
536 
537     version = open_libtracker ();
538 
539     if (version == TRACKER_0_8)
540     {
541         tracker_client = tracker_client_new (TRACKER_CLIENT_ENABLE_WARNINGS, G_MAXINT);
542     }
543     else
544     {
545         if (! tracker_connect)
546             return NULL;
547 
548         tracker_client = tracker_connect (FALSE, -1);
549     }
550 
551     if (!tracker_client)
552     {
553         return NULL;
554     }
555 
556     if (version == TRACKER_0_6)
557     {
558         GError *err = NULL;
559 
560         tracker_get_version (tracker_client, &err);
561 
562         if (err != NULL)
563         {
564             g_error_free (err);
565             tracker_disconnect (tracker_client);
566             return NULL;
567         }
568     }
569 
570     engine = g_object_new (CAJA_TYPE_SEARCH_ENGINE_TRACKER, NULL);
571 
572     engine->details->client = tracker_client;
573     engine->details->query_pending = FALSE;
574     engine->details->version = version;
575 
576     return CAJA_SEARCH_ENGINE (engine);
577 }
578