[glib/wip/gdesktopappinfo] desktopappinfo: Support search on unindexed directories



commit e9cdac332e314a9380dff58b9b782d0687081d13
Author: Ryan Lortie <desrt desrt ca>
Date:   Wed Sep 25 23:39:19 2013 -0400

    desktopappinfo: Support search on unindexed directories
    
    A hashtable is _so_ wrong here, but it's good enough for now...

 gio/gdesktopappinfo.c |  517 ++++++++++++++++++++++++++++++++++---------------
 1 files changed, 362 insertions(+), 155 deletions(-)
---
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index 049f08d..4eb490c 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -300,6 +300,7 @@ typedef struct
   guint16                     key_id_map_length;
   guint16                    *locale_ids;
   guint16                     desktop_entry_id;
+  GHashTable                 *memory_index;
 } DesktopFileDir;
 
 static DesktopFileDir *desktop_file_dirs;
@@ -395,6 +396,168 @@ add_to_table_if_appropriate (GHashTable      *apps,
   g_hash_table_insert (apps, g_strdup (info->filename), info);
 }
 
+
+enum
+{
+  DESKTOP_KEY_nil,
+
+  /* NB: must keep this list sorted */
+  DESKTOP_KEY_Actions,
+  DESKTOP_KEY_Categories,
+  DESKTOP_KEY_Comment,
+  DESKTOP_KEY_DBusActivatable,
+  DESKTOP_KEY_Exec,
+  DESKTOP_KEY_GenericName,
+  DESKTOP_KEY_Hidden,
+  DESKTOP_KEY_Icon,
+  DESKTOP_KEY_Keywords,
+  DESKTOP_KEY_MimeType,
+  DESKTOP_KEY_Name,
+  DESKTOP_KEY_NoDisplay,
+  DESKTOP_KEY_NotShowIn,
+  DESKTOP_KEY_OnlyShowIn,
+  DESKTOP_KEY_Path,
+  DESKTOP_KEY_StartupNotify,
+  DESKTOP_KEY_StartupWMClass,
+  DESKTOP_KEY_Terminal,
+  DESKTOP_KEY_TryExec,
+  DESKTOP_KEY_Type,
+  DESKTOP_KEY_Version,
+  DESKTOP_KEY_X_GNOME_FullName,
+
+  N_DESKTOP_KEYS
+};
+
+const gchar *desktop_key_names[N_DESKTOP_KEYS];
+
+const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
+  /* NB: no harm in repeating numbers in case of a tie */
+  [DESKTOP_KEY_Name]             = 1,
+  [DESKTOP_KEY_GenericName]      = 2,
+  [DESKTOP_KEY_Keywords]         = 3,
+  [DESKTOP_KEY_X_GNOME_FullName] = 4,
+  [DESKTOP_KEY_Comment]          = 5
+};
+
+#define DESKTOP_KEY_NUM_MATCH_CATEGORIES  5
+
+static void
+desktop_key_init (void)
+{
+  if G_LIKELY (desktop_key_names[1])
+    return;
+
+  desktop_key_names[DESKTOP_KEY_Actions]                = g_intern_static_string ("Actions");
+  desktop_key_names[DESKTOP_KEY_Categories]             = g_intern_static_string ("Categories");
+  desktop_key_names[DESKTOP_KEY_Comment]                = g_intern_static_string ("Comment");
+  desktop_key_names[DESKTOP_KEY_DBusActivatable]        = g_intern_static_string ("DBusActivatable");
+  desktop_key_names[DESKTOP_KEY_Exec]                   = g_intern_static_string ("Exec");
+  desktop_key_names[DESKTOP_KEY_GenericName]            = g_intern_static_string ("GenericName");
+  desktop_key_names[DESKTOP_KEY_Hidden]                 = g_intern_static_string ("Hidden");
+  desktop_key_names[DESKTOP_KEY_Icon]                   = g_intern_static_string ("Icon");
+  desktop_key_names[DESKTOP_KEY_Keywords]               = g_intern_static_string ("Keywords");
+  desktop_key_names[DESKTOP_KEY_MimeType]               = g_intern_static_string ("MimeType");
+  desktop_key_names[DESKTOP_KEY_Name]                   = g_intern_static_string ("Name");
+  desktop_key_names[DESKTOP_KEY_NoDisplay]              = g_intern_static_string ("NoDisplay");
+  desktop_key_names[DESKTOP_KEY_NotShowIn]              = g_intern_static_string ("NotShowIn");
+  desktop_key_names[DESKTOP_KEY_OnlyShowIn]             = g_intern_static_string ("OnlyShowIn");
+  desktop_key_names[DESKTOP_KEY_Path]                   = g_intern_static_string ("Path");
+  desktop_key_names[DESKTOP_KEY_StartupNotify]          = g_intern_static_string ("StartupNotify");
+  desktop_key_names[DESKTOP_KEY_StartupWMClass]         = g_intern_static_string ("StartupWMClass");
+  desktop_key_names[DESKTOP_KEY_Terminal]               = g_intern_static_string ("Terminal");
+  desktop_key_names[DESKTOP_KEY_TryExec]                = g_intern_static_string ("TryExec");
+  desktop_key_names[DESKTOP_KEY_Type]                   = g_intern_static_string ("Type");
+  desktop_key_names[DESKTOP_KEY_Version]                = g_intern_static_string ("Version");
+  desktop_key_names[DESKTOP_KEY_X_GNOME_FullName]       = g_intern_static_string ("X-GNOME-FullName");
+}
+
+static void
+insert_into_list (GSList      **categories,
+                  const gchar  *item,
+                  gint          into_bucket,
+                  gint          max_hits)
+{
+  gint n_hits = 0;
+  gint i;
+
+  for (i = 0; i <= into_bucket; i++)
+    {
+      GSList *node;
+
+      /* If we have enough hits from previous categories, stop.
+       *
+       * Note.  This check will not be applied after counting up the
+       * hits in categories[into_bucket] and that's intentional: we
+       * don't want to toss out our new item if it has the same score as
+       * items we're already returning.
+       *
+       * That might mean that we get more than max_hits items, but
+       * that's the desired behaviour.
+       */
+      if (max_hits >= 0 && n_hits >= max_hits)
+        return;
+
+      /* We want to do the dup-checking in all cases, though */
+      for (node = categories[i]; node; node = node->next)
+        {
+          /* Pointer comparison is good enough because we're using the
+           * name from the desktop index (where it is unique) and
+           * because we'll never see the same desktop file name across
+           * multiple indexes due to our use of masking.
+           */
+          if (node->data == item)
+            return;
+
+          n_hits++;
+        }
+    }
+
+  /* No duplicate found at this level or any before, and not too many
+   * items in earlier buckets, so add our item.
+   */
+  categories[into_bucket] = g_slist_prepend (categories[into_bucket], (gchar *) item);
+  n_hits++;
+
+  /* We may need to remove ourselves from a later category if we already
+   * had a weaker hit for this item.
+   *
+   * We may also need to blow away an entire category if n_hits is big
+   * enough.
+   */
+  for (i = into_bucket + 1; i < DESKTOP_KEY_NUM_MATCH_CATEGORIES; i++)
+    {
+      if (!categories[i])
+        continue;
+
+      if (max_hits >= 0 && n_hits >= max_hits)
+        {
+          g_slist_free (categories[i]);
+          categories[i] = NULL;
+        }
+      else
+        {
+          GSList **ptr;
+
+          for (ptr = &categories[i]; *ptr; ptr = &(*ptr)->next)
+            if ((*ptr)->data == item)
+              {
+                /* Found us! */
+                *ptr = g_slist_delete_link (*ptr, *ptr);
+                n_hits--;
+
+                /* Since we effectively didn't add any items this time
+                 * around, we know we will not need to blow away later
+                 * categories.  We also know that we will not appear in
+                 * a later category, since we appeared here.
+                 *
+                 * So, we're done.
+                 */
+                return;
+              }
+        }
+    }
+}
+
 /* Support for unindexed DesktopFileDirs {{{2 */
 static void
 get_apps_from_dir (GHashTable **apps,
@@ -482,92 +645,217 @@ desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
     }
 }
 
+typedef struct _MemoryIndexEntry MemoryIndexEntry;
+typedef GHashTable MemoryIndex;
+
+struct _MemoryIndexEntry
+{
+  const gchar      *app_name; /* pointer to the hashtable key */
+  gint              match_category;
+  MemoryIndexEntry *next;
+};
+
 static void
-desktop_file_dir_unindexed_search (DesktopFileDir  *dir,
-                                   GSList         **categories,
-                                   const gchar     *term,
-                                   gint             max_hits)
+memory_index_entry_free (gpointer data)
 {
-  /* TODO */
-}
+  MemoryIndexEntry *mie = data;
 
-/* Support for indexed DesktopFileDirs {{{2 */
+  while (mie)
+    {
+      MemoryIndexEntry *next = mie->next;
 
-enum
+      g_slice_free (MemoryIndexEntry, mie);
+      mie = next;
+    }
+}
+
+static void
+memory_index_add_token (MemoryIndex *mi,
+                        const gchar *token_start,
+                        const gchar *token_end,
+                        gint         match_category,
+                        const gchar *app_name)
 {
-  DESKTOP_KEY_nil,
+  MemoryIndexEntry *mie, *first;
+  gchar *normal;
+  gchar *folded;
 
-  /* NB: must keep this list sorted */
-  DESKTOP_KEY_Actions,
-  DESKTOP_KEY_Categories,
-  DESKTOP_KEY_Comment,
-  DESKTOP_KEY_DBusActivatable,
-  DESKTOP_KEY_Exec,
-  DESKTOP_KEY_GenericName,
-  DESKTOP_KEY_Hidden,
-  DESKTOP_KEY_Icon,
-  DESKTOP_KEY_Keywords,
-  DESKTOP_KEY_MimeType,
-  DESKTOP_KEY_Name,
-  DESKTOP_KEY_NoDisplay,
-  DESKTOP_KEY_NotShowIn,
-  DESKTOP_KEY_OnlyShowIn,
-  DESKTOP_KEY_Path,
-  DESKTOP_KEY_StartupNotify,
-  DESKTOP_KEY_StartupWMClass,
-  DESKTOP_KEY_Terminal,
-  DESKTOP_KEY_TryExec,
-  DESKTOP_KEY_Type,
-  DESKTOP_KEY_Version,
-  DESKTOP_KEY_X_GNOME_FullName,
+  normal = g_utf8_normalize (token_start, token_end - token_start, G_NORMALIZE_ALL_COMPOSE);
 
-  N_DESKTOP_KEYS
-};
+  /* TODO: Invent time machine.  Converse with Mustafa Ataturk... */
+  if (strstr (normal, "ı") || strstr (normal, "İ"))
+    {
+      gchar *s = normal;
+      GString *tmp;
 
-const gchar *desktop_key_names[N_DESKTOP_KEYS];
+      tmp = g_string_new (NULL);
 
-const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
-  /* NB: no harm in repeating numbers in case of a tie */
-  [DESKTOP_KEY_Name]             = 1,
-  [DESKTOP_KEY_GenericName]      = 2,
-  [DESKTOP_KEY_Keywords]         = 3,
-  [DESKTOP_KEY_X_GNOME_FullName] = 4,
-  [DESKTOP_KEY_Comment]          = 5
-};
+      while (*s)
+        {
+          gchar *i, *I, *e;
 
-#define DESKTOP_KEY_NUM_MATCH_CATEGORIES  5
+          i = strstr (s, "ı");
+          I = strstr (s, "İ");
+
+          if (!i && !I)
+            break;
+          else if (i && !I)
+            e = i;
+          else if (I && !i)
+            e = I;
+          else if (i < I)
+            e = i;
+          else
+            e = I;
+
+          g_string_append_len (tmp, s, e - s);
+          g_string_append_c (tmp, 'i');
+          s = g_utf8_next_char (e);
+        }
+
+      g_string_append (tmp, s);
+      g_free (normal);
+      normal = g_string_free (tmp, FALSE);
+    }
+
+  folded = g_utf8_casefold (normal, -1);
+
+  mie = g_slice_new (MemoryIndexEntry);
+  mie->app_name = app_name;
+  mie->match_category = match_category;
+
+  first = g_hash_table_lookup (mi, folded);
+
+  if (first)
+    {
+      mie->next = first->next;
+      first->next = mie;
+      g_free (folded); /* not using this... */
+    }
+  else
+    {
+      mie->next = NULL;
+      g_hash_table_insert (mi, folded, mie);
+    }
+
+  g_free (normal);
+}
 
 static void
-desktop_key_init (void)
+memory_index_add_string (MemoryIndex *mi,
+                         const gchar *string,
+                         gint         match_category,
+                         const gchar *app_name)
 {
-  if G_LIKELY (desktop_key_names[1])
+  const gchar *start = NULL;
+  const gchar *s;
+
+  for (s = string; *s; s = g_utf8_next_char (s))
+    {
+      gunichar c = g_utf8_get_char (s);
+
+      if (start == NULL)
+        {
+          if (g_unichar_isalnum (c))
+            start = s;
+        }
+      else
+        {
+          if (!g_unichar_isalnum (c))
+            {
+              memory_index_add_token (mi, start, s, match_category, app_name);
+              start = NULL;
+            }
+        }
+    }
+
+  if (start)
+    memory_index_add_token (mi, start, s, match_category, app_name);
+}
+
+static MemoryIndex *
+memory_index_new (void)
+{
+  return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, memory_index_entry_free);
+}
+
+static void
+desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
+{
+  GHashTableIter iter;
+  gpointer app, path;
+
+  desktop_key_init ();
+
+  dir->memory_index = memory_index_new ();
+
+  /* Nothing to search? */
+  if (dir->app_names == NULL)
     return;
 
-  desktop_key_names[DESKTOP_KEY_Actions]                = g_intern_static_string ("Actions");
-  desktop_key_names[DESKTOP_KEY_Categories]             = g_intern_static_string ("Categories");
-  desktop_key_names[DESKTOP_KEY_Comment]                = g_intern_static_string ("Comment");
-  desktop_key_names[DESKTOP_KEY_DBusActivatable]        = g_intern_static_string ("DBusActivatable");
-  desktop_key_names[DESKTOP_KEY_Exec]                   = g_intern_static_string ("Exec");
-  desktop_key_names[DESKTOP_KEY_GenericName]            = g_intern_static_string ("GenericName");
-  desktop_key_names[DESKTOP_KEY_Hidden]                 = g_intern_static_string ("Hidden");
-  desktop_key_names[DESKTOP_KEY_Icon]                   = g_intern_static_string ("Icon");
-  desktop_key_names[DESKTOP_KEY_Keywords]               = g_intern_static_string ("Keywords");
-  desktop_key_names[DESKTOP_KEY_MimeType]               = g_intern_static_string ("MimeType");
-  desktop_key_names[DESKTOP_KEY_Name]                   = g_intern_static_string ("Name");
-  desktop_key_names[DESKTOP_KEY_NoDisplay]              = g_intern_static_string ("NoDisplay");
-  desktop_key_names[DESKTOP_KEY_NotShowIn]              = g_intern_static_string ("NotShowIn");
-  desktop_key_names[DESKTOP_KEY_OnlyShowIn]             = g_intern_static_string ("OnlyShowIn");
-  desktop_key_names[DESKTOP_KEY_Path]                   = g_intern_static_string ("Path");
-  desktop_key_names[DESKTOP_KEY_StartupNotify]          = g_intern_static_string ("StartupNotify");
-  desktop_key_names[DESKTOP_KEY_StartupWMClass]         = g_intern_static_string ("StartupWMClass");
-  desktop_key_names[DESKTOP_KEY_Terminal]               = g_intern_static_string ("Terminal");
-  desktop_key_names[DESKTOP_KEY_TryExec]                = g_intern_static_string ("TryExec");
-  desktop_key_names[DESKTOP_KEY_Type]                   = g_intern_static_string ("Type");
-  desktop_key_names[DESKTOP_KEY_Version]                = g_intern_static_string ("Version");
-  desktop_key_names[DESKTOP_KEY_X_GNOME_FullName]       = g_intern_static_string ("X-GNOME-FullName");
+  g_hash_table_iter_init (&iter, dir->app_names);
+  while (g_hash_table_iter_next (&iter, &app, &path))
+    {
+      GKeyFile *key_file;
+
+      key_file = g_key_file_new ();
+
+      if (g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, NULL))
+        {
+          /* Index the interesting keys... */
+          gint i;
+
+          for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
+            {
+              gchar *value;
+
+              if (!desktop_key_match_category[i])
+                continue;
+
+              value = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_names[i], NULL, 
NULL);
+
+              if (value)
+                memory_index_add_string (dir->memory_index, value, desktop_key_match_category[i], app);
+
+              g_free (value);
+            }
+        }
+
+      g_key_file_free (key_file);
+    }
 }
 
 static void
+desktop_file_dir_unindexed_search (DesktopFileDir  *dir,
+                                   GSList         **categories,
+                                   const gchar     *term,
+                                   gint             max_hits)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  if (!dir->memory_index)
+    desktop_file_dir_unindexed_setup_search (dir);
+
+  g_hash_table_iter_init (&iter, dir->memory_index);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      MemoryIndexEntry *mie = value;
+
+      if (!g_str_has_prefix (key, term))
+        continue;
+
+      while (mie)
+        {
+          insert_into_list (categories, mie->app_name, mie->match_category - 1, max_hits);
+          mie = mie->next;
+        }
+    }
+}
+
+/* Support for indexed DesktopFileDirs {{{2 */
+
+static void
 desktop_file_dir_indexed_init (DesktopFileDir *dir)
 {
   desktop_key_init ();
@@ -739,93 +1027,6 @@ desktop_file_dir_indexed_get_all (DesktopFileDir *dir,
 }
 
 static void
-insert_into_list (GSList      **categories,
-                  const gchar  *item,
-                  gint          into_bucket,
-                  gint          max_hits)
-{
-  gint n_hits = 0;
-  gint i;
-
-  for (i = 0; i <= into_bucket; i++)
-    {
-      GSList *node;
-
-      /* If we have enough hits from previous categories, stop.
-       *
-       * Note.  This check will not be applied after counting up the
-       * hits in categories[into_bucket] and that's intentional: we
-       * don't want to toss out our new item if it has the same score as
-       * items we're already returning.
-       *
-       * That might mean that we get more than max_hits items, but
-       * that's the desired behaviour.
-       */
-      if (max_hits >= 0 && n_hits >= max_hits)
-        return;
-
-      /* We want to do the dup-checking in all cases, though */
-      for (node = categories[i]; node; node = node->next)
-        {
-          /* Pointer comparison is good enough because we're using the
-           * name from the desktop index (where it is unique) and
-           * because we'll never see the same desktop file name across
-           * multiple indexes due to our use of masking.
-           */
-          if (node->data == item)
-            return;
-
-          n_hits++;
-        }
-    }
-
-  /* No duplicate found at this level or any before, and not too many
-   * items in earlier buckets, so add our item.
-   */
-  categories[into_bucket] = g_slist_prepend (categories[into_bucket], (gchar *) item);
-  n_hits++;
-
-  /* We may need to remove ourselves from a later category if we already
-   * had a weaker hit for this item.
-   *
-   * We may also need to blow away an entire category if n_hits is big
-   * enough.
-   */
-  for (i = into_bucket + 1; i < DESKTOP_KEY_NUM_MATCH_CATEGORIES; i++)
-    {
-      if (!categories[i])
-        continue;
-
-      if (max_hits >= 0 && n_hits >= max_hits)
-        {
-          g_slist_free (categories[i]);
-          categories[i] = NULL;
-        }
-      else
-        {
-          GSList **ptr;
-
-          for (ptr = &categories[i]; *ptr; ptr = &(*ptr)->next)
-            if ((*ptr)->data == item)
-              {
-                /* Found us! */
-                *ptr = g_slist_delete_link (*ptr, *ptr);
-                n_hits--;
-
-                /* Since we effectively didn't add any items this time
-                 * around, we know we will not need to blow away later
-                 * categories.  We also know that we will not appear in
-                 * a later category, since we appeared here.
-                 *
-                 * So, we're done.
-                 */
-                return;
-              }
-        }
-    }
-}
-
-static void
 desktop_file_dir_indexed_search (DesktopFileDir  *dir,
                                  GSList         **categories,
                                  const gchar     *term,
@@ -976,6 +1177,12 @@ desktop_file_dir_reset (DesktopFileDir *dir)
       dir->locale_ids = NULL;
     }
 
+  if (dir->memory_index)
+    {
+      g_hash_table_unref (dir->memory_index);
+      dir->memory_index = NULL;
+    }
+
   dir->key_id_map_length = 0;
   dir->desktop_entry_id = 0;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]