]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtksearchenginetracker.c
GtkTextView: don't popdown a bubble if we don't have one
[~andy/gtk] / gtk / gtksearchenginetracker.c
index 68346fa43e8f9a5982d7c6cd2176316bdd56178e..eae66703e87d941a8a890f870540dc476a5f0f2e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009-2010 Nokia <ivan.frade@nokia.com>
+ * Copyright (C) 2009-2011 Nokia <ivan.frade@nokia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -12,8 +12,7 @@
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  *
  * Authors: Jürg Billeter <juerg.billeter@codethink.co.uk>
  *          Martyn Russell <martyn@lanedo.com>
 
 #include "config.h"
 
+#include <string.h>
+
 #include <gio/gio.h>
 #include <gmodule.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
 
 #include "gtksearchenginetracker.h"
 
+#define DBUS_SERVICE_RESOURCES   "org.freedesktop.Tracker1"
+#define DBUS_PATH_RESOURCES      "/org/freedesktop/Tracker1/Resources"
+#define DBUS_INTERFACE_RESOURCES "org.freedesktop.Tracker1.Resources"
+
+#define DBUS_SERVICE_STATUS      "org.freedesktop.Tracker1"
+#define DBUS_PATH_STATUS         "/org/freedesktop/Tracker1/Status"
+#define DBUS_INTERFACE_STATUS    "org.freedesktop.Tracker1.Status"
+
+/* Time in second to wait for service before deciding it's not available */
+#define WAIT_TIMEOUT_SECONDS 1
+
+/* Time in second to wait for query results to come back */
+#define QUERY_TIMEOUT_SECONDS 10
+
 /* If defined, we use fts:match, this has to be enabled in Tracker to
  * work which it usually is. The alternative is to undefine it and
  * use filename matching instead. This doesn't use the content of the
  */
 #undef FTS_MATCHING
 
-#define MODULE_FILENAME "libtracker-sparql-0.10.so.0"
-
-#define MODULE_MAP(a)   { #a, (gpointer *)&a }
-
-/* Connection object */
-typedef struct _TrackerSparqlConnection TrackerSparqlConnection;
-
-#define TRACKER_SPARQL_TYPE_CONNECTION (tracker_sparql_connection_get_type ())
-#define TRACKER_SPARQL_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRACKER_SPARQL_TYPE_CONNECTION, TrackerSparqlConnection))
-
-/* Cursor object */
-typedef struct _TrackerSparqlCursor TrackerSparqlCursor;
-
-#define TRACKER_SPARQL_TYPE_CURSOR (tracker_sparql_cursor_get_type ())
-#define TRACKER_SPARQL_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRACKER_SPARQL_TYPE_CURSOR, TrackerSparqlCursor))
-
-/* API */
-static GType                     (*tracker_sparql_connection_get_type)     (void) = NULL;
-static TrackerSparqlConnection * (*tracker_sparql_connection_get)          (GCancellable             *cancellable,
-                                                                            GError                  **error) = NULL;
-static void                      (*tracker_sparql_connection_query_async)  (TrackerSparqlConnection  *self,
-                                                                            const gchar              *sparql,
-                                                                            GCancellable             *cancellable,
-                                                                            GAsyncReadyCallback       callback,
-                                                                            gpointer                  user_data) = NULL;
-static TrackerSparqlCursor *     (*tracker_sparql_connection_query_finish) (TrackerSparqlConnection  *self,
-                                                                            GAsyncResult             *_res_,
-                                                                            GError                  **error) = NULL;
-static GType                     (*tracker_sparql_cursor_get_type)         (void) = NULL;
-static void                      (*tracker_sparql_cursor_next_async)       (TrackerSparqlCursor      *self,
-                                                                            GCancellable             *cancellable,
-                                                                            GAsyncReadyCallback       callback,
-                                                                            gpointer                  user_data) = NULL;
-static gboolean                  (*tracker_sparql_cursor_next_finish)      (TrackerSparqlCursor      *self,
-                                                                            GAsyncResult             *_res_,
-                                                                            GError                  **error) = NULL;
-static const gchar *             (*tracker_sparql_cursor_get_string)       (TrackerSparqlCursor      *self,
-                                                                            gint                     *column,
-                                                                            glong                    *length) = NULL;
-static gchar *                   (*tracker_sparql_escape_string)           (const gchar              *literal) = NULL;
-
-static struct TrackerFunctions
-{
-       const char *name;
-       gpointer *pointer;
-} funcs[] = {
-       MODULE_MAP (tracker_sparql_connection_get_type),
-       MODULE_MAP (tracker_sparql_connection_get),
-       MODULE_MAP (tracker_sparql_connection_query_async),
-       MODULE_MAP (tracker_sparql_connection_query_finish),
-       MODULE_MAP (tracker_sparql_cursor_get_type),
-       MODULE_MAP (tracker_sparql_cursor_next_async),
-       MODULE_MAP (tracker_sparql_cursor_next_finish),
-       MODULE_MAP (tracker_sparql_cursor_get_string),
-       MODULE_MAP (tracker_sparql_escape_string)
-};
-
-static gboolean
-init (void)
-{
-  static gboolean inited = FALSE;
-  gint i;
-  GModule *m;
-  GModuleFlags flags;
-
-  if (inited)
-         return TRUE;
-
-  flags = G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL;
-
-  /* Only support 0.10 onwards */
-  if ((m = g_module_open (MODULE_FILENAME, flags)) == NULL)
-         {
-                 g_debug ("No tracker backend available or it is not new enough");
-                 g_debug ("Only available using '%s'", MODULE_FILENAME);
-                 return FALSE;
-         }
-
-  inited = TRUE;
-
-  /* Check for the symbols we need */
-  for (i = 0; i < G_N_ELEMENTS (funcs); i++)
-         {
-                 if (!g_module_symbol (m, funcs[i].name, funcs[i].pointer))
-                         {
-                                 g_warning ("Missing symbol '%s' in libtracker-sparql\n",
-                                            funcs[i].name);
-                                 g_module_close (m);
-
-                                 for (i = 0; i < G_N_ELEMENTS (funcs); i++)
-                                         funcs[i].pointer = NULL;
-
-                                 return FALSE;
-                         }
-           }
-
-  g_debug ("Loaded Tracker library and all required symbols");
-
-  return TRUE;
-}
-
 /*
  * GtkSearchEngineTracker object
  */
 struct _GtkSearchEngineTrackerPrivate
 {
-  TrackerSparqlConnection *connection;
-       GCancellable *cancellable;
+  GDBusConnection *connection;
+  GCancellable *cancellable;
   GtkQuery *query;
   gboolean query_pending;
 };
 
 G_DEFINE_TYPE (GtkSearchEngineTracker, _gtk_search_engine_tracker, GTK_TYPE_SEARCH_ENGINE);
 
-static void cursor_callback (GObject      *object,
-                             GAsyncResult *result,
-                             gpointer      user_data);
-
 static void
 finalize (GObject *object)
 {
   GtkSearchEngineTracker *tracker;
 
+  g_debug ("Finalizing GtkSearchEngineTracker");
+
   tracker = GTK_SEARCH_ENGINE_TRACKER (object);
 
   if (tracker->priv->cancellable)
-         {
-                 g_cancellable_cancel (tracker->priv->cancellable);
-                 g_object_unref (tracker->priv->cancellable);
-                 tracker->priv->cancellable = NULL;
-         }
+    {
+      g_cancellable_cancel (tracker->priv->cancellable);
+      g_object_unref (tracker->priv->cancellable);
+      tracker->priv->cancellable = NULL;
+    }
 
   if (tracker->priv->query)
     {
@@ -174,87 +88,200 @@ finalize (GObject *object)
     }
 
   if (tracker->priv->connection)
-         {
-                 g_object_unref (tracker->priv->connection);
-                 tracker->priv->connection = NULL;
-         }
+    {
+      g_object_unref (tracker->priv->connection);
+      tracker->priv->connection = NULL;
+    }
 
   G_OBJECT_CLASS (_gtk_search_engine_tracker_parent_class)->finalize (object);
 }
 
-static void
-cursor_next (GtkSearchEngineTracker *tracker,
-             TrackerSparqlCursor    *cursor)
+static GDBusConnection *
+get_connection (void)
 {
-       tracker_sparql_cursor_next_async (cursor,
-                                         tracker->priv->cancellable,
-                                         cursor_callback,
-                                         tracker);
-}
-
-static void
-cursor_callback (GObject      *object,
-                 GAsyncResult *result,
-                 gpointer      user_data)
-{
-  GtkSearchEngineTracker *tracker;
+  GDBusConnection *connection;
   GError *error = NULL;
-  TrackerSparqlCursor *cursor;
-       GList *hits;
-  gboolean success;
+  GVariant *reply;
 
-  gdk_threads_enter ();
+  /* Normally I hate sync calls with UIs, but we need to return NULL
+   * or a GtkSearchEngine as a result of this function.
+   */
+  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
 
-  tracker = GTK_SEARCH_ENGINE_TRACKER (user_data);
+  if (error)
+    {
+      g_debug ("Couldn't connect to D-Bus session bus, %s", error->message);
+      g_error_free (error);
+      return NULL;
+    }
 
-       cursor = TRACKER_SPARQL_CURSOR (object);
-       success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+  /* If connection is set, we know it worked. */
+  g_debug ("Finding out if Tracker is available via D-Bus...");
+
+  /* We only wait 1 second max, we expect it to be very fast. If we
+   * don't get a response by then, clearly we're replaying a journal
+   * or cleaning up the DB internally. Either way, services is not
+   * available.
+   *
+   * We use the sync call here because we don't expect to be waiting
+   * long enough to block UI painting.
+   */
+  reply = g_dbus_connection_call_sync (connection,
+                                       DBUS_SERVICE_STATUS,
+                                       DBUS_PATH_STATUS,
+                                       DBUS_INTERFACE_STATUS,
+                                       "Wait",
+                                       NULL,
+                                       NULL,
+                                       G_DBUS_CALL_FLAGS_NONE,
+                                       WAIT_TIMEOUT_SECONDS * 1000,
+                                       NULL,
+                                       &error);
 
   if (error)
     {
-      _gtk_search_engine_error (GTK_SEARCH_ENGINE (tracker), error->message);
-
+      g_debug ("Tracker is not available, %s", error->message);
       g_error_free (error);
+      g_object_unref (connection);
+      return NULL;
+    }
 
-      if (cursor)
-             g_object_unref (cursor);
+  g_variant_unref (reply);
 
-      gdk_threads_leave ();
-      return;
-    }
+  g_debug ("Tracker is ready");
 
-  if (!success)
-         {
-                 _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
+  return connection;
+}
 
-                 if (cursor)
-                         g_object_unref (cursor);
+static void
+get_query_results (GtkSearchEngineTracker *engine,
+                   const gchar            *sparql,
+                   GAsyncReadyCallback     callback,
+                   gpointer                user_data)
+{
+  g_dbus_connection_call (engine->priv->connection,
+                          DBUS_SERVICE_RESOURCES,
+                          DBUS_PATH_RESOURCES,
+                          DBUS_INTERFACE_RESOURCES,
+                          "SparqlQuery",
+                          g_variant_new ("(s)", sparql),
+                          NULL,
+                          G_DBUS_CALL_FLAGS_NONE,
+                          QUERY_TIMEOUT_SECONDS * 1000,
+                          engine->priv->cancellable,
+                          callback,
+                          user_data);
+}
 
-                 gdk_threads_leave ();
-                 return;
-         }
+/* Stolen from libtracker-common */
+static GList *
+string_list_to_gslist (gchar **strv)
+{
+  GList *list;
+  gsize i;
 
-  /* We iterate result by result, not n at a time. */
-  hits = g_list_append (NULL, (gchar*) tracker_sparql_cursor_get_string (cursor, 0, NULL));
-  _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (tracker), hits);
-  g_list_free (hits);
+  list = NULL;
 
-  /* Get next */
-  cursor_next (tracker, cursor);
+  for (i = 0; strv[i]; i++)
+    list = g_list_prepend (list, g_strdup (strv[i]));
 
-  gdk_threads_leave ();
+  return g_list_reverse (list);
+}
+
+/* Stolen from libtracker-sparql */
+static gchar *
+sparql_escape_string (const gchar *literal)
+{
+  GString *str;
+  const gchar *p;
+
+  g_return_val_if_fail (literal != NULL, NULL);
+
+  str = g_string_new ("");
+  p = literal;
+
+  while (TRUE)
+     {
+      gsize len;
+
+      if (!((*p) != '\0'))
+        break;
+
+      len = strcspn ((const gchar *) p, "\t\n\r\b\f\"\\");
+      g_string_append_len (str, (const gchar *) p, (gssize) ((glong) len));
+      p = p + len;
+
+      switch (*p)
+        {
+        case '\t':
+          g_string_append (str, "\\t");
+          break;
+        case '\n':
+          g_string_append (str, "\\n");
+          break;
+        case '\r':
+          g_string_append (str, "\\r");
+          break;
+        case '\b':
+          g_string_append (str, "\\b");
+          break;
+        case '\f':
+          g_string_append (str, "\\f");
+          break;
+        case '"':
+          g_string_append (str, "\\\"");
+          break;
+        case '\\':
+          g_string_append (str, "\\\\");
+          break;
+        default:
+          continue;
+        }
+
+      p++;
+     }
+  return g_string_free (str, FALSE);
+ }
 
+static void
+sparql_append_string_literal (GString     *sparql,
+                              const gchar *str)
+{
+  gchar *s;
+
+  s = sparql_escape_string (str);
+
+  g_string_append_c (sparql, '"');
+  g_string_append (sparql, s);
+  g_string_append_c (sparql, '"');
+
+  g_free (s);
+}
+
+static void
+sparql_append_string_literal_lower_case (GString     *sparql,
+                                         const gchar *str)
+{
+  gchar *s;
+
+  s = g_utf8_strdown (str, -1);
+  sparql_append_string_literal (sparql, s);
+  g_free (s);
 }
 
 static void
 query_callback (GObject      *object,
-                GAsyncResult *result,
+                GAsyncResult *res,
                 gpointer      user_data)
 {
   GtkSearchEngineTracker *tracker;
-  TrackerSparqlConnection *connection;
-  TrackerSparqlCursor *cursor;
+  GList *hits;
+  GVariant *reply;
+  GVariant *r;
+  GVariantIter iter;
+  gchar **result;
   GError *error = NULL;
+  gint i, n;
 
   gdk_threads_enter ();
 
@@ -262,13 +289,7 @@ query_callback (GObject      *object,
 
   tracker->priv->query_pending = FALSE;
 
-  connection = TRACKER_SPARQL_CONNECTION (object);
-       cursor = tracker_sparql_connection_query_finish (connection,
-                                                        result,
-                                                        &error);
-
-       g_debug ("Query returned cursor:%p", cursor);
-
+  reply = g_dbus_connection_call_finish (tracker->priv->connection, res, &error);
   if (error)
     {
       _gtk_search_engine_error (GTK_SEARCH_ENGINE (tracker), error->message);
@@ -277,59 +298,68 @@ query_callback (GObject      *object,
       return;
     }
 
-  if (!cursor)
-         {
-                 _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
-                 gdk_threads_leave ();
-                 return;
-         }
-
-  cursor_next (tracker, cursor);
-  gdk_threads_leave ();
-}
+  if (!reply)
+    {
+      _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
+      gdk_threads_leave ();
+      return;
+    }
 
-static void
-sparql_append_string_literal (GString     *sparql,
-                              const gchar *str)
-{
-  gchar *s;
+  r = g_variant_get_child_value (reply, 0);
+  g_variant_iter_init (&iter, r);
+  n = g_variant_iter_n_children (&iter);
+  result = g_new0 (gchar *, n + 1);
+  for (i = 0; i < n; i++)
+    {
+      GVariant *v;
+      const gchar **strv;
 
-  s = tracker_sparql_escape_string (str);
+      v = g_variant_iter_next_value (&iter);
+      strv = g_variant_get_strv (v, NULL);
+      result[i] = (gchar*)strv[0];
+      g_free (strv);
+    }
 
-  g_string_append_c (sparql, '"');
-  g_string_append (sparql, s);
-  g_string_append_c (sparql, '"');
+  /* We iterate result by result, not n at a time. */
+  hits = string_list_to_gslist (result);
+  _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (tracker), hits);
+  _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
+  g_list_free (hits);
+  g_free (result);
+  g_variant_unref (reply);
+  g_variant_unref (r);
 
-  g_free (s);
+  gdk_threads_leave ();
 }
 
 static void
 gtk_search_engine_tracker_start (GtkSearchEngine *engine)
 {
   GtkSearchEngineTracker *tracker;
-  gchar        *search_text, *location_uri;
+  gchar *search_text;
+#ifdef FTS_MATCHING
+  gchar *location_uri;
+#endif
   GString *sparql;
 
   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
 
   if (tracker->priv->query_pending)
-         {
-                 g_debug ("Attempt to start a new search while one is pending, doing nothing");
-                 return;
-         }
+    {
+      g_debug ("Attempt to start a new search while one is pending, doing nothing");
+      return;
+    }
 
   if (tracker->priv->query == NULL)
-         {
-                 g_debug ("Attempt to start a new search with no GtkQuery, doing nothing");
-                 return;
-         }
+    {
+      g_debug ("Attempt to start a new search with no GtkQuery, doing nothing");
+      return;
+    }
 
   search_text = _gtk_query_get_text (tracker->priv->query);
-  location_uri = _gtk_query_get_location (tracker->priv->query);
-
-  g_debug ("Query starting, search criteria:'%s', location:'%s'", search_text, location_uri);
 
 #ifdef FTS_MATCHING
+  location_uri = _gtk_query_get_location (tracker->priv->query);
   /* Using FTS: */
   sparql = g_string_new ("SELECT nie:url(?urn) "
                          "WHERE {"
@@ -339,11 +369,11 @@ gtk_search_engine_tracker_start (GtkSearchEngine *engine)
   sparql_append_string_literal (sparql, search_text);
 
   if (location_uri)
-         {
-                 g_string_append (sparql, " . FILTER (fn:starts-with(nie:url(?urn),");
-                 sparql_append_string_literal (sparql, location_uri);
-                 g_string_append (sparql, "))");
-         }
+    {
+      g_string_append (sparql, " . FILTER (fn:starts-with(nie:url(?urn),");
+      sparql_append_string_literal (sparql, location_uri);
+      g_string_append (sparql, "))");
+    }
 
   g_string_append (sparql, " } ORDER BY DESC(fts:rank(?urn)) ASC(nie:url(?urn))");
 #else  /* FTS_MATCHING */
@@ -352,22 +382,19 @@ gtk_search_engine_tracker_start (GtkSearchEngine *engine)
                          "WHERE {"
                          "  ?urn a nfo:FileDataObject ;"
                          "    tracker:available true ."
-                         "  FILTER (fn:contains(nfo:fileName(?urn),");
-  sparql_append_string_literal (sparql, search_text);
+                         "  FILTER (fn:contains(fn:lower-case(nfo:fileName(?urn)),");
+  sparql_append_string_literal_lower_case (sparql, search_text);
 
-  g_string_append (sparql, 
+  g_string_append (sparql,
                    "))"
                    "} ORDER BY DESC(nie:url(?urn)) DESC(nfo:fileName(?urn))");
 #endif /* FTS_MATCHING */
 
-  tracker_sparql_connection_query_async (tracker->priv->connection,
-                                         sparql->str,
-                                         tracker->priv->cancellable,
-                                         query_callback,
-                                         tracker);
-  g_string_free (sparql, TRUE);
-
   tracker->priv->query_pending = TRUE;
+
+  get_query_results (tracker, sparql->str, query_callback, tracker);
+
+  g_string_free (sparql, TRUE);
   g_free (search_text);
 }
 
@@ -378,11 +405,9 @@ gtk_search_engine_tracker_stop (GtkSearchEngine *engine)
 
   tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
 
-  g_debug ("Query stopping");
-
   if (tracker->priv->query && tracker->priv->query_pending)
     {
-           g_cancellable_cancel (tracker->priv->cancellable);
+      g_cancellable_cancel (tracker->priv->cancellable);
       tracker->priv->query_pending = FALSE;
     }
 }
@@ -425,7 +450,8 @@ _gtk_search_engine_tracker_class_init (GtkSearchEngineTrackerClass *class)
   engine_class->stop = gtk_search_engine_tracker_stop;
   engine_class->is_indexed = gtk_search_engine_tracker_is_indexed;
 
-  g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineTrackerPrivate));
+  g_type_class_add_private (gobject_class,
+                            sizeof (GtkSearchEngineTrackerPrivate));
 }
 
 static void
@@ -441,34 +467,20 @@ GtkSearchEngine *
 _gtk_search_engine_tracker_new (void)
 {
   GtkSearchEngineTracker *engine;
-  TrackerSparqlConnection *connection;
-  GCancellable *cancellable;
-  GError *error = NULL;
+  GDBusConnection *connection;
 
-  if (!init ())
-         return NULL;
+  g_debug ("--");
 
-  g_debug ("Creating GtkSearchEngineTracker...");
+  connection = get_connection ();
+  if (!connection)
+    return NULL;
 
-  cancellable = g_cancellable_new ();
-       connection = tracker_sparql_connection_get (cancellable, &error);
-
-       if (error)
-               {
-                       g_warning ("Could not establish a connection to Tracker: %s", error->message);
-                       g_error_free (error);
-                       return NULL;
-               }
-       else if (!connection)
-         {
-                 g_warning ("Could not establish a connection to Tracker, no TrackerSparqlConnection was returned");
-                 return NULL;
-         }
+  g_debug ("Creating GtkSearchEngineTracker...");
 
   engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_TRACKER, NULL);
 
   engine->priv->connection = connection;
-  engine->priv->cancellable = cancellable;
+  engine->priv->cancellable = g_cancellable_new ();
   engine->priv->query_pending = FALSE;
 
   return GTK_SEARCH_ENGINE (engine);