]> Pileus Git - ~andy/gtk/blob - gtk/gtksearchenginetracker.c
tracker-search-engine: Improve search query to order by rank and title
[~andy/gtk] / gtk / gtksearchenginetracker.c
1 /*
2  * Copyright (C) 2005 Mr Jamie McCracken
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  *
18  * Author: Jamie McCracken <jamiemcc@gnome.org>
19  *
20  * Based on nautilus-search-engine-tracker.c
21  */
22
23 #include "config.h"
24 #include <gmodule.h>
25 #include "gtksearchenginetracker.h"
26
27 /* we dlopen() libtracker at runtime */
28
29 typedef struct _TrackerClient TrackerClient;
30
31 typedef enum
32 {
33   TRACKER_UNAVAILABLE = 0,
34   TRACKER_0_6 = 1 << 0,
35   TRACKER_0_7 = 1 << 1,
36   TRACKER_0_8 = 1 << 2,
37   TRACKER_0_9 = 1 << 3
38 } TrackerVersion;
39
40
41 /* Tracker 0.6 API */
42 typedef void (*TrackerArrayReply) (char **result, GError *error, gpointer user_data);
43
44 static TrackerClient * (*tracker_connect) (gboolean enable_warnings, gint timeout) = NULL;
45 static void            (*tracker_disconnect) (TrackerClient *client) = NULL;
46 static int             (*tracker_get_version) (TrackerClient *client, GError **error) = NULL;
47 static void            (*tracker_cancel_last_call) (TrackerClient *client) = NULL;
48
49 static void (*tracker_search_metadata_by_text_async) (TrackerClient *client, 
50                                                       const char *query, 
51                                                       TrackerArrayReply callback, 
52                                                       gpointer user_data) = NULL;
53 static void (*tracker_search_metadata_by_text_and_location_async) (TrackerClient *client, 
54                                                                    const char *query, 
55                                                                    const char *location, 
56                                                                    TrackerArrayReply callback, 
57                                                                    gpointer user_data) = NULL;
58 /* Tracker 0.7->0.9 API */
59 typedef enum {
60         TRACKER_CLIENT_ENABLE_WARNINGS = 1 << 0
61 } TrackerClientFlags;
62
63 typedef void (*TrackerReplyGPtrArray) (GPtrArray *result,
64                                        GError    *error,
65                                        gpointer   user_data);
66
67 static TrackerClient *  (*tracker_client_new)                   (TrackerClientFlags      flags,
68                                                                  gint                    timeout) = NULL;
69 static gchar *          (*tracker_sparql_escape)                (const gchar            *str) = NULL;
70 static guint            (*tracker_resources_sparql_query_async) (TrackerClient          *client,
71                                                                  const gchar            *query,
72                                                                  TrackerReplyGPtrArray   callback,
73                                                                  gpointer                user_data) = NULL;
74
75
76 static struct TrackerDlMapping
77 {
78   const char *fn_name;
79   gpointer *fn_ptr_ref;
80   TrackerVersion versions;
81 } tracker_dl_mapping[] =
82 {
83 #define MAP(a,v) { #a, (gpointer *)&a, v }
84   MAP (tracker_connect, TRACKER_0_6 | TRACKER_0_7),
85   MAP (tracker_disconnect, TRACKER_0_6 | TRACKER_0_7),
86   MAP (tracker_get_version, TRACKER_0_6),
87   MAP (tracker_cancel_last_call, TRACKER_0_6 | TRACKER_0_7 | TRACKER_0_8 | TRACKER_0_9),
88   MAP (tracker_search_metadata_by_text_async, TRACKER_0_6 | TRACKER_0_7),
89   MAP (tracker_search_metadata_by_text_and_location_async, TRACKER_0_6 | TRACKER_0_7),
90   MAP (tracker_client_new, TRACKER_0_8 | TRACKER_0_9),
91   MAP (tracker_sparql_escape, TRACKER_0_8 | TRACKER_0_9),
92   MAP (tracker_resources_sparql_query_async, TRACKER_0_8 | TRACKER_0_9)
93 #undef MAP
94 };
95
96 static TrackerVersion
97 open_libtracker (void)
98 {
99   static gboolean done = FALSE;
100   static TrackerVersion version = TRACKER_UNAVAILABLE;
101   gpointer x;
102
103   if (!done)
104     {
105       gint i;
106       GModule *tracker;
107       GModuleFlags flags;
108       
109       done = TRUE;
110       flags = G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL;
111
112       /* So this is the order:
113        *
114        * - 0.9 (latest unstable)
115        * - 0.8 (stable)
116        * - 0.7 (unstable, 0.6 sucks so badly)
117        * - 0.6 (stable)
118        */
119       if ((tracker = g_module_open ("libtracker-client-0.9.so.0", flags)) != NULL)
120         version = TRACKER_0_9;
121       else if ((tracker = g_module_open ("libtracker-client-0.8.so.0", flags)) != NULL)
122         version = TRACKER_0_8;
123       else if ((tracker = g_module_open ("libtracker-client-0.7.so.0", flags)) != NULL)
124         version = TRACKER_0_7;
125       else if ((tracker = g_module_open ("libtrackerclient.so.0", flags)) != NULL)
126         version = TRACKER_0_6;
127       else
128         {
129           g_debug ("No tracker backend available");
130           return TRACKER_UNAVAILABLE;
131         }
132
133       for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
134         {
135           if ((tracker_dl_mapping[i].versions & version) == 0)
136             continue;
137
138           if (!g_module_symbol (tracker, 
139                                 tracker_dl_mapping[i].fn_name,
140                                 tracker_dl_mapping[i].fn_ptr_ref))
141             {
142               g_warning ("Missing symbol '%s' in libtracker\n",
143                          tracker_dl_mapping[i].fn_name);
144               g_module_close (tracker);
145
146               for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
147                 tracker_dl_mapping[i].fn_ptr_ref = NULL;
148
149               return TRACKER_UNAVAILABLE;
150             }
151         }
152     }
153
154   return version;
155 }
156
157 struct _GtkSearchEngineTrackerPrivate 
158 {
159   GtkQuery      *query;
160   TrackerClient *client;
161   gboolean       query_pending;
162   TrackerVersion version;
163 };
164
165 G_DEFINE_TYPE (GtkSearchEngineTracker, _gtk_search_engine_tracker, GTK_TYPE_SEARCH_ENGINE);
166
167
168 static void
169 finalize (GObject *object)
170 {
171   GtkSearchEngineTracker *tracker;
172   
173   tracker = GTK_SEARCH_ENGINE_TRACKER (object);
174   
175   if (tracker->priv->query) 
176     {
177       g_object_unref (tracker->priv->query);
178       tracker->priv->query = NULL;
179     }
180
181   if (tracker->priv->version == TRACKER_0_8 ||
182       tracker->priv->version == TRACKER_0_9)
183     g_object_unref (tracker->priv->client);
184   else
185     tracker_disconnect (tracker->priv->client);
186
187   G_OBJECT_CLASS (_gtk_search_engine_tracker_parent_class)->finalize (object);
188 }
189
190
191 /* stolen from tracker sources, tracker.c */
192 static void
193 sparql_append_string_literal (GString     *sparql,
194                               const gchar *str)
195 {
196   gchar *s;
197
198   s = tracker_sparql_escape (str);
199
200   g_string_append_c (sparql, '"');
201   g_string_append (sparql, s);
202   g_string_append_c (sparql, '"');
203
204   g_free (s);
205 }
206
207
208 static void
209 search_callback (gpointer results,
210                  GError  *error, 
211                  gpointer user_data)
212 {
213   GtkSearchEngineTracker *tracker;
214   gchar **results_p;
215   GList *hit_uris;
216   GPtrArray *OUT_result;
217   gchar *uri;
218   gint i;
219   
220   tracker = GTK_SEARCH_ENGINE_TRACKER (user_data);
221   hit_uris = NULL;
222   
223   tracker->priv->query_pending = FALSE;
224
225   if (error) 
226     {
227       _gtk_search_engine_error (GTK_SEARCH_ENGINE (tracker), error->message);
228       g_error_free (error);
229       return;
230     }
231
232   if (!results)
233     return;
234
235   if (tracker->priv->version == TRACKER_0_8 ||
236       tracker->priv->version == TRACKER_0_9)
237     {
238       OUT_result = (GPtrArray*) results;
239
240       for (i = 0; i < OUT_result->len; i++)
241         {
242           uri = g_strdup (((gchar **) OUT_result->pdata[i])[0]);
243           if (uri)
244             hit_uris = g_list_prepend (hit_uris, uri);
245         }
246
247       g_ptr_array_foreach (OUT_result, (GFunc) g_free, NULL);
248       g_ptr_array_free (OUT_result, TRUE);
249     }
250   else
251     {
252       for (results_p = results; *results_p; results_p++)
253         {
254           if (tracker->priv->version == TRACKER_0_6)
255             uri = g_filename_to_uri (*results_p, NULL, NULL);
256           else
257             uri = g_strdup (*results_p);
258
259           if (uri)
260             hit_uris = g_list_prepend (hit_uris, uri);
261         }
262       g_strfreev ((gchar **) results);
263     }
264
265   _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (tracker), hit_uris);
266   _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
267
268   g_list_foreach (hit_uris, (GFunc) g_free, NULL);
269   g_list_free (hit_uris);
270 }
271
272
273 static void
274 gtk_search_engine_tracker_start (GtkSearchEngine *engine)
275 {
276   GtkSearchEngineTracker *tracker;
277   gchar *search_text, *location, *location_uri;
278   GString *sparql;
279
280   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
281
282   if (tracker->priv->query_pending)
283     return;
284
285   if (tracker->priv->query == NULL)
286     return;
287         
288   search_text = _gtk_query_get_text (tracker->priv->query);
289   location_uri = _gtk_query_get_location (tracker->priv->query);
290
291   location = NULL;
292   if (location_uri)
293     {
294       if (tracker->priv->version == TRACKER_0_6)
295         {
296           location = g_filename_from_uri (location_uri, NULL, NULL);
297           g_free (location_uri);
298         }
299       else
300         location = location_uri;
301     }
302
303   if (tracker->priv->version == TRACKER_0_8 ||
304       tracker->priv->version == TRACKER_0_9)
305     {
306       sparql = g_string_new ("SELECT nie:url(?urn) WHERE { ?urn a nfo:FileDataObject; fts:match ");
307       sparql_append_string_literal (sparql, search_text);
308       if (location)
309         {
310           g_string_append (sparql, " . FILTER (fn:starts-with(nie:url(?urn),");
311           sparql_append_string_literal (sparql, location);
312           g_string_append (sparql, "))");
313         }
314       g_string_append (sparql, " } ORDER BY DESC(fts:rank(?urn)) ASC(nie:url(?urn))");
315
316       tracker_resources_sparql_query_async (tracker->priv->client,
317                                             sparql->str,
318                                             (TrackerReplyGPtrArray) search_callback,
319                                             tracker);
320       g_string_free (sparql, TRUE);
321     }
322   else 
323     {
324       if (location)
325         {
326           tracker_search_metadata_by_text_and_location_async (tracker->priv->client,
327                                                               search_text,
328                                                               location,
329                                                               (TrackerArrayReply) search_callback,
330                                                               tracker);
331         }
332       else
333         {
334           tracker_search_metadata_by_text_async (tracker->priv->client,
335                                                  search_text,
336                                                  (TrackerArrayReply) search_callback,
337                                                  tracker);
338         }
339     }
340
341   tracker->priv->query_pending = TRUE;
342   g_free (search_text);
343   g_free (location);
344 }
345
346 static void
347 gtk_search_engine_tracker_stop (GtkSearchEngine *engine)
348 {
349   GtkSearchEngineTracker *tracker;
350   
351   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
352   
353   if (tracker->priv->query && tracker->priv->query_pending) 
354     {
355       tracker_cancel_last_call (tracker->priv->client);
356       tracker->priv->query_pending = FALSE;
357     }
358 }
359
360 static gboolean
361 gtk_search_engine_tracker_is_indexed (GtkSearchEngine *engine)
362 {
363   return TRUE;
364 }
365
366 static void
367 gtk_search_engine_tracker_set_query (GtkSearchEngine *engine, 
368                                      GtkQuery        *query)
369 {
370   GtkSearchEngineTracker *tracker;
371   
372   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
373   
374   if (query) 
375     g_object_ref (query);
376
377   if (tracker->priv->query)
378     g_object_unref (tracker->priv->query);
379
380   tracker->priv->query = query;
381 }
382
383 static void
384 _gtk_search_engine_tracker_class_init (GtkSearchEngineTrackerClass *class)
385 {
386   GObjectClass *gobject_class;
387   GtkSearchEngineClass *engine_class;
388   
389   gobject_class = G_OBJECT_CLASS (class);
390   gobject_class->finalize = finalize;
391   
392   engine_class = GTK_SEARCH_ENGINE_CLASS (class);
393   engine_class->set_query = gtk_search_engine_tracker_set_query;
394   engine_class->start = gtk_search_engine_tracker_start;
395   engine_class->stop = gtk_search_engine_tracker_stop;
396   engine_class->is_indexed = gtk_search_engine_tracker_is_indexed;
397
398   g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineTrackerPrivate));
399 }
400
401 static void
402 _gtk_search_engine_tracker_init (GtkSearchEngineTracker *engine)
403 {
404   engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerPrivate);
405 }
406
407
408 GtkSearchEngine *
409 _gtk_search_engine_tracker_new (void)
410 {
411   GtkSearchEngineTracker *engine;
412   TrackerClient *tracker_client;
413   TrackerVersion version;
414   GError *err = NULL;
415
416   version = open_libtracker ();
417
418   if (version == TRACKER_0_8 ||
419       version == TRACKER_0_9)
420     {
421       tracker_client = tracker_client_new (TRACKER_CLIENT_ENABLE_WARNINGS, G_MAXINT);
422     }
423   else
424     {
425       if (!tracker_connect)
426         return NULL;
427
428       tracker_client = tracker_connect (FALSE, -1);
429     }
430
431   if (!tracker_client)
432     return NULL;
433
434
435   if (version == TRACKER_0_6)
436     {
437       if (!tracker_get_version)
438         return NULL;
439
440       tracker_get_version (tracker_client, &err);
441
442       if (err != NULL)
443         {
444           g_error_free (err);
445           tracker_disconnect (tracker_client);
446           return NULL;
447         }
448     }
449
450   engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_TRACKER, NULL);
451
452   engine->priv->client = tracker_client;
453   engine->priv->query_pending = FALSE;
454   engine->priv->version = version;
455   
456   return GTK_SEARCH_ENGINE (engine);
457 }