]> Pileus Git - ~andy/gtk/blob - gtk/gtksearchenginetracker.c
a2b4b93a1962b724982ce43c6951dce68ffff955
[~andy/gtk] / gtk / gtksearchenginetracker.c
1 /*
2  * Copyright (C) 2009-2011 Nokia <ivan.frade@nokia.com>
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  * Authors: Jürg Billeter <juerg.billeter@codethink.co.uk>
19  *          Martyn Russell <martyn@lanedo.com>
20  *
21  * Based on nautilus-search-engine-tracker.c
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27
28 #include <gio/gio.h>
29 #include <gmodule.h>
30 #include <gdk/gdk.h>
31 #include <gtk/gtk.h>
32
33 #include "gtksearchenginetracker.h"
34
35 #define DBUS_SERVICE_RESOURCES   "org.freedesktop.Tracker1"
36 #define DBUS_PATH_RESOURCES      "/org/freedesktop/Tracker1/Resources"
37 #define DBUS_INTERFACE_RESOURCES "org.freedesktop.Tracker1.Resources"
38
39 #define DBUS_SERVICE_STATUS      "org.freedesktop.Tracker1"
40 #define DBUS_PATH_STATUS         "/org/freedesktop/Tracker1/Status"
41 #define DBUS_INTERFACE_STATUS    "org.freedesktop.Tracker1.Status"
42
43 /* Time in second to wait for service before deciding it's not available */
44 #define WAIT_TIMEOUT_SECONDS 1
45
46 /* Time in second to wait for query results to come back */
47 #define QUERY_TIMEOUT_SECONDS 10
48
49 /* If defined, we use fts:match, this has to be enabled in Tracker to
50  * work which it usually is. The alternative is to undefine it and
51  * use filename matching instead. This doesn't use the content of the
52  * file however.
53  */
54 #undef FTS_MATCHING
55
56 /*
57  * GtkSearchEngineTracker object
58  */
59 struct _GtkSearchEngineTrackerPrivate
60 {
61   GDBusConnection *connection;
62   GCancellable *cancellable;
63   GtkQuery *query;
64   gboolean query_pending;
65 };
66
67 G_DEFINE_TYPE (GtkSearchEngineTracker, _gtk_search_engine_tracker, GTK_TYPE_SEARCH_ENGINE);
68
69 static void
70 finalize (GObject *object)
71 {
72   GtkSearchEngineTracker *tracker;
73
74   g_debug ("Finalizing GtkSearchEngineTracker");
75
76   tracker = GTK_SEARCH_ENGINE_TRACKER (object);
77
78   if (tracker->priv->cancellable)
79     {
80       g_cancellable_cancel (tracker->priv->cancellable);
81       g_object_unref (tracker->priv->cancellable);
82       tracker->priv->cancellable = NULL;
83     }
84
85   if (tracker->priv->query)
86     {
87       g_object_unref (tracker->priv->query);
88       tracker->priv->query = NULL;
89     }
90
91   if (tracker->priv->connection)
92     {
93       g_object_unref (tracker->priv->connection);
94       tracker->priv->connection = NULL;
95     }
96
97   G_OBJECT_CLASS (_gtk_search_engine_tracker_parent_class)->finalize (object);
98 }
99
100 static GDBusConnection *
101 get_connection (void)
102 {
103   GDBusConnection *connection;
104   GError *error = NULL;
105   GVariant *reply;
106
107   /* Normally I hate sync calls with UIs, but we need to return NULL
108    * or a GtkSearchEngine as a result of this function.
109    */
110   connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
111
112   if (error)
113     {
114       g_debug ("Couldn't connect to D-Bus session bus, %s", error->message);
115       g_error_free (error);
116       return NULL;
117     }
118
119   /* If connection is set, we know it worked. */
120   g_debug ("Finding out if Tracker is available via D-Bus...");
121
122   /* We only wait 1 second max, we expect it to be very fast. If we
123    * don't get a response by then, clearly we're replaying a journal
124    * or cleaning up the DB internally. Either way, services is not
125    * available.
126    *
127    * We use the sync call here because we don't expect to be waiting
128    * long enough to block UI painting.
129    */
130   reply = g_dbus_connection_call_sync (connection,
131                                        DBUS_SERVICE_STATUS,
132                                        DBUS_PATH_STATUS,
133                                        DBUS_INTERFACE_STATUS,
134                                        "Wait",
135                                        NULL,
136                                        NULL,
137                                        G_DBUS_CALL_FLAGS_NONE,
138                                        WAIT_TIMEOUT_SECONDS * 1000,
139                                        NULL,
140                                        &error);
141
142   if (error)
143     {
144       g_debug ("Tracker is not available, %s", error->message);
145       g_error_free (error);
146       g_object_unref (connection);
147       return NULL;
148     }
149
150   g_variant_unref (reply);
151
152   g_debug ("Tracker is ready");
153
154   return connection;
155 }
156
157 static void
158 get_query_results (GtkSearchEngineTracker *engine,
159                    const gchar            *sparql,
160                    GAsyncReadyCallback     callback,
161                    gpointer                user_data)
162 {
163   g_dbus_connection_call (engine->priv->connection,
164                           DBUS_SERVICE_RESOURCES,
165                           DBUS_PATH_RESOURCES,
166                           DBUS_INTERFACE_RESOURCES,
167                           "SparqlQuery",
168                           g_variant_new ("(s)", sparql),
169                           NULL,
170                           G_DBUS_CALL_FLAGS_NONE,
171                           QUERY_TIMEOUT_SECONDS * 1000,
172                           engine->priv->cancellable,
173                           callback,
174                           user_data);
175 }
176
177 /* Stolen from libtracker-common */
178 static GList *
179 string_list_to_gslist (gchar **strv)
180 {
181   GList *list;
182   gsize i;
183
184   list = NULL;
185
186   for (i = 0; strv[i]; i++)
187     list = g_list_prepend (list, g_strdup (strv[i]));
188
189   return g_list_reverse (list);
190 }
191
192 /* Stolen from libtracker-sparql */
193 static gchar *
194 sparql_escape_string (const gchar *literal)
195 {
196   GString *str;
197   const gchar *p;
198
199   g_return_val_if_fail (literal != NULL, NULL);
200
201   str = g_string_new ("");
202   p = literal;
203
204   while (TRUE)
205      {
206       gsize len;
207
208       if (!((*p) != '\0'))
209         break;
210
211       len = strcspn ((const gchar *) p, "\t\n\r\b\f\"\\");
212       g_string_append_len (str, (const gchar *) p, (gssize) ((glong) len));
213       p = p + len;
214
215       switch (*p)
216         {
217         case '\t':
218           g_string_append (str, "\\t");
219           break;
220         case '\n':
221           g_string_append (str, "\\n");
222           break;
223         case '\r':
224           g_string_append (str, "\\r");
225           break;
226         case '\b':
227           g_string_append (str, "\\b");
228           break;
229         case '\f':
230           g_string_append (str, "\\f");
231           break;
232         case '"':
233           g_string_append (str, "\\\"");
234           break;
235         case '\\':
236           g_string_append (str, "\\\\");
237           break;
238         default:
239           continue;
240         }
241
242       p++;
243      }
244   return g_string_free (str, FALSE);
245  }
246
247 static void
248 sparql_append_string_literal (GString     *sparql,
249                               const gchar *str)
250 {
251   gchar *s;
252
253   s = sparql_escape_string (str);
254
255   g_string_append_c (sparql, '"');
256   g_string_append (sparql, s);
257   g_string_append_c (sparql, '"');
258
259   g_free (s);
260 }
261
262 static void
263 sparql_append_string_literal_lower_case (GString     *sparql,
264                                          const gchar *str)
265 {
266   gchar *s;
267
268   s = g_utf8_strdown (str, -1);
269   sparql_append_string_literal (sparql, s);
270   g_free (s);
271 }
272
273 static void
274 query_callback (GObject      *object,
275                 GAsyncResult *res,
276                 gpointer      user_data)
277 {
278   GtkSearchEngineTracker *tracker;
279   GList *hits;
280   GVariant *reply;
281   GVariant *r;
282   GVariantIter iter;
283   gchar **result;
284   GError *error = NULL;
285   gint i, n;
286
287   gdk_threads_enter ();
288
289   tracker = GTK_SEARCH_ENGINE_TRACKER (user_data);
290
291   tracker->priv->query_pending = FALSE;
292
293   reply = g_dbus_connection_call_finish (tracker->priv->connection, res, &error);
294   if (error)
295     {
296       _gtk_search_engine_error (GTK_SEARCH_ENGINE (tracker), error->message);
297       g_error_free (error);
298       gdk_threads_leave ();
299       return;
300     }
301
302   if (!reply)
303     {
304       _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
305       gdk_threads_leave ();
306       return;
307     }
308
309   r = g_variant_get_child_value (reply, 0);
310   g_variant_iter_init (&iter, r);
311   n = g_variant_iter_n_children (&iter);
312   result = g_new0 (gchar *, n + 1);
313   for (i = 0; i < n; i++)
314     {
315       GVariant *v;
316       const gchar **strv;
317
318       v = g_variant_iter_next_value (&iter);
319       strv = g_variant_get_strv (v, NULL);
320       result[i] = (gchar*)strv[0];
321       g_free (strv);
322     }
323
324   /* We iterate result by result, not n at a time. */
325   hits = string_list_to_gslist (result);
326   _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (tracker), hits);
327   _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
328   g_list_free (hits);
329   g_free (result);
330   g_variant_unref (reply);
331   g_variant_unref (r);
332
333   gdk_threads_leave ();
334 }
335
336 static void
337 gtk_search_engine_tracker_start (GtkSearchEngine *engine)
338 {
339   GtkSearchEngineTracker *tracker;
340   gchar *search_text;
341 #ifdef FTS_MATCHING
342   gchar *location_uri;
343 #endif
344   GString *sparql;
345
346   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
347
348   if (tracker->priv->query_pending)
349     {
350       g_debug ("Attempt to start a new search while one is pending, doing nothing");
351       return;
352     }
353
354   if (tracker->priv->query == NULL)
355     {
356       g_debug ("Attempt to start a new search with no GtkQuery, doing nothing");
357       return;
358     }
359
360   search_text = _gtk_query_get_text (tracker->priv->query);
361
362 #ifdef FTS_MATCHING
363   location_uri = _gtk_query_get_location (tracker->priv->query);
364   /* Using FTS: */
365   sparql = g_string_new ("SELECT nie:url(?urn) "
366                          "WHERE {"
367                          "  ?urn a nfo:FileDataObject ;"
368                          "  tracker:available true ; "
369                          "  fts:match ");
370   sparql_append_string_literal (sparql, search_text);
371
372   if (location_uri)
373     {
374       g_string_append (sparql, " . FILTER (fn:starts-with(nie:url(?urn),");
375       sparql_append_string_literal (sparql, location_uri);
376       g_string_append (sparql, "))");
377     }
378
379   g_string_append (sparql, " } ORDER BY DESC(fts:rank(?urn)) ASC(nie:url(?urn))");
380 #else  /* FTS_MATCHING */
381   /* Using filename matching: */
382   sparql = g_string_new ("SELECT nie:url(?urn) "
383                          "WHERE {"
384                          "  ?urn a nfo:FileDataObject ;"
385                          "    tracker:available true ."
386                          "  FILTER (fn:contains(fn:lower-case(nfo:fileName(?urn)),");
387   sparql_append_string_literal_lower_case (sparql, search_text);
388
389   g_string_append (sparql,
390                    "))"
391                    "} ORDER BY DESC(nie:url(?urn)) DESC(nfo:fileName(?urn))");
392 #endif /* FTS_MATCHING */
393
394   tracker->priv->query_pending = TRUE;
395
396   get_query_results (tracker, sparql->str, query_callback, tracker);
397
398   g_string_free (sparql, TRUE);
399   g_free (search_text);
400 }
401
402 static void
403 gtk_search_engine_tracker_stop (GtkSearchEngine *engine)
404 {
405   GtkSearchEngineTracker *tracker;
406
407   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
408
409   if (tracker->priv->query && tracker->priv->query_pending)
410     {
411       g_cancellable_cancel (tracker->priv->cancellable);
412       tracker->priv->query_pending = FALSE;
413     }
414 }
415
416 static gboolean
417 gtk_search_engine_tracker_is_indexed (GtkSearchEngine *engine)
418 {
419   return TRUE;
420 }
421
422 static void
423 gtk_search_engine_tracker_set_query (GtkSearchEngine *engine,
424                                      GtkQuery        *query)
425 {
426   GtkSearchEngineTracker *tracker;
427
428   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
429
430   if (query)
431     g_object_ref (query);
432
433   if (tracker->priv->query)
434     g_object_unref (tracker->priv->query);
435
436   tracker->priv->query = query;
437 }
438
439 static void
440 _gtk_search_engine_tracker_class_init (GtkSearchEngineTrackerClass *class)
441 {
442   GObjectClass *gobject_class;
443   GtkSearchEngineClass *engine_class;
444
445   gobject_class = G_OBJECT_CLASS (class);
446   gobject_class->finalize = finalize;
447
448   engine_class = GTK_SEARCH_ENGINE_CLASS (class);
449   engine_class->set_query = gtk_search_engine_tracker_set_query;
450   engine_class->start = gtk_search_engine_tracker_start;
451   engine_class->stop = gtk_search_engine_tracker_stop;
452   engine_class->is_indexed = gtk_search_engine_tracker_is_indexed;
453
454   g_type_class_add_private (gobject_class,
455                             sizeof (GtkSearchEngineTrackerPrivate));
456 }
457
458 static void
459 _gtk_search_engine_tracker_init (GtkSearchEngineTracker *engine)
460 {
461   engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine,
462                                               GTK_TYPE_SEARCH_ENGINE_TRACKER,
463                                               GtkSearchEngineTrackerPrivate);
464 }
465
466
467 GtkSearchEngine *
468 _gtk_search_engine_tracker_new (void)
469 {
470   GtkSearchEngineTracker *engine;
471   GDBusConnection *connection;
472
473   g_debug ("--");
474
475   connection = get_connection ();
476   if (!connection)
477     return NULL;
478
479   g_debug ("Creating GtkSearchEngineTracker...");
480
481   engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_TRACKER, NULL);
482
483   engine->priv->connection = connection;
484   engine->priv->cancellable = g_cancellable_new ();
485   engine->priv->query_pending = FALSE;
486
487   return GTK_SEARCH_ENGINE (engine);
488 }