[glib] gdesktopappinfo: keep a list of files in the dirs



commit 86ce3bf48e40756a360b13e18493a15d8d1bf5ae
Author: Ryan Lortie <desrt desrt ca>
Date:   Sat Jul 27 16:04:56 2013 -0400

    gdesktopappinfo: keep a list of files in the dirs
    
    In each DesktopFileDir, store a list of desktop files for that
    directory.  This speeds up opening desktop files by name because we can
    skip statting in directories that we know don't have the file and also
    speeds up _get_all() because we can avoid enumeration.
    
    This also improves our support for dealing with names like
    'kde4/kate.desktop' (equivalent to kde4-kate.desktop) since we find out
    about all of these files are the start and don't need to guess about
    which '-' to change to a '/'.  It also means that we can easily deal
    with more than one level of such prefixes.
    
    We use a file monitor to watch for changes, invalidating our lists when
    we notice them.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=711520

 gio/gdesktopappinfo.c |  434 +++++++++++++++++++++++++++++++++++++------------
 1 files changed, 328 insertions(+), 106 deletions(-)
---
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index 141a89a..c67186f 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -47,6 +47,7 @@
 #include "glibintl.h"
 #include "giomodule-priv.h"
 #include "gappinfo.h"
+#include "glocaldirectorymonitor.h"
 
 
 /**
@@ -145,10 +146,187 @@ static gchar *g_desktop_env = NULL;
 typedef struct
 {
   gchar                      *path;
+  GLocalDirectoryMonitor     *monitor;
+  GHashTable                 *app_names;
+  gboolean                    is_setup;
 } DesktopFileDir;
 
 static DesktopFileDir *desktop_file_dirs;
 static guint           n_desktop_file_dirs;
+static GMutex          desktop_file_dir_lock;
+
+/* Monitor 'changed' signal handler {{{2 */
+static void desktop_file_dir_reset (DesktopFileDir *dir);
+
+static void
+desktop_file_dir_changed (GFileMonitor      *monitor,
+                          GFile             *file,
+                          GFile             *other_file,
+                          GFileMonitorEvent  event_type,
+                          gpointer           user_data)
+{
+  DesktopFileDir *dir = user_data;
+
+  /* We are not interested in receiving notifications forever just
+   * because someone asked about one desktop file once.
+   *
+   * After we receive the first notification, reset the dir, destroying
+   * the monitor.  We will take this as a hint, next time that we are
+   * asked, that we need to check if everything is up to date.
+   */
+  g_mutex_lock (&desktop_file_dir_lock);
+
+  desktop_file_dir_reset (dir);
+
+  g_mutex_unlock (&desktop_file_dir_lock);
+}
+
+/* Internal utility functions {{{2 */
+
+/*< internal >
+ * desktop_file_dir_app_name_is_masked:
+ * @dir: a #DesktopFileDir
+ * @app_name: an application ID
+ *
+ * Checks if @app_name is masked for @dir.
+ *
+ * An application is masked if a similarly-named desktop file exists in
+ * a desktop file directory with higher precedence.  Masked desktop
+ * files should be ignored.
+ */
+static gboolean
+desktop_file_dir_app_name_is_masked (DesktopFileDir *dir,
+                                     const gchar    *app_name)
+{
+  while (dir > desktop_file_dirs)
+    {
+      dir--;
+
+      if (dir->app_names && g_hash_table_contains (dir->app_names, app_name))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+/*< internal >
+ * add_to_table_if_appropriate:
+ * @apps: a string to GDesktopAppInfo hash table
+ * @app_name: the name of the application
+ * @info: a #GDesktopAppInfo, or NULL
+ *
+ * If @info is non-%NULL and non-hidden, then add it to @apps, using
+ * @app_name as a key.
+ *
+ * If @info is non-%NULL then this function will consume the passed-in
+ * reference.
+ */
+static void
+add_to_table_if_appropriate (GHashTable      *apps,
+                             const gchar     *app_name,
+                             GDesktopAppInfo *info)
+{
+  if (!info)
+    return;
+
+  if (info->hidden)
+    {
+      g_object_unref (info);
+      return;
+    }
+
+  g_free (info->desktop_id);
+  info->desktop_id = g_strdup (app_name);
+
+  g_hash_table_insert (apps, g_strdup (info->desktop_id), info);
+}
+
+/* Support for unindexed DesktopFileDirs {{{2 */
+static void
+get_apps_from_dir (GHashTable **apps,
+                   const char  *dirname,
+                   const char  *prefix)
+{
+  const char *basename;
+  GDir *dir;
+
+  dir = g_dir_open (dirname, 0, NULL);
+
+  if (dir == NULL)
+    return;
+
+  while ((basename = g_dir_read_name (dir)) != NULL)
+    {
+      gchar *filename;
+
+      filename = g_build_filename (dirname, basename, NULL);
+
+      if (g_str_has_suffix (basename, ".desktop"))
+        {
+          gchar *app_name;
+
+          app_name = g_strconcat (prefix, basename, NULL);
+
+          if (*apps == NULL)
+            *apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+          g_hash_table_insert (*apps, app_name, g_strdup (filename));
+        }
+      else if (g_file_test (filename, G_FILE_TEST_IS_DIR))
+        {
+          gchar *subprefix;
+
+          subprefix = g_strconcat (prefix, basename, "-", NULL);
+          get_apps_from_dir (apps, filename, subprefix);
+          g_free (subprefix);
+        }
+
+      g_free (filename);
+    }
+
+  g_dir_close (dir);
+}
+
+static void
+desktop_file_dir_unindexed_init (DesktopFileDir *dir)
+{
+  get_apps_from_dir (&dir->app_names, dir->path, "");
+}
+
+static GDesktopAppInfo *
+desktop_file_dir_unindexed_get_app (DesktopFileDir *dir,
+                                    const gchar    *desktop_id)
+{
+  const gchar *filename;
+
+  filename = g_hash_table_lookup (dir->app_names, desktop_id);
+
+  if (!filename)
+    return NULL;
+
+  return g_desktop_app_info_new_from_filename (filename);
+}
+
+static void
+desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
+                                    GHashTable     *apps)
+{
+  GHashTableIter iter;
+  gpointer app_name;
+  gpointer filename;
+
+  if (dir->app_names == NULL)
+    return;
+
+  g_hash_table_iter_init (&iter, dir->app_names);
+  while (g_hash_table_iter_next (&iter, &app_name, &filename))
+    {
+      if (desktop_file_dir_app_name_is_masked (dir, app_name))
+        continue;
+
+      add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename (filename));
+    }
+}
 
 /* DesktopFileDir "API" {{{2 */
 
@@ -171,12 +349,105 @@ desktop_file_dir_create (GArray      *array,
   g_array_append_val (array, dir);
 }
 
-/* Global setup API {{{2 */
+/*< internal >
+ * desktop_file_dir_reset:
+ * @dir: a #DesktopFileDir
+ *
+ * Cleans up @dir, releasing most resources that it was using.
+ */
+static void
+desktop_file_dir_reset (DesktopFileDir *dir)
+{
+  if (dir->monitor)
+    {
+      g_signal_handlers_disconnect_by_func (dir->monitor, desktop_file_dir_changed, dir);
+      g_object_unref (dir->monitor);
+      dir->monitor = NULL;
+    }
+
+  if (dir->app_names)
+    {
+      g_hash_table_unref (dir->app_names);
+      dir->app_names = NULL;
+    }
+
+  dir->is_setup = FALSE;
+}
 
+/*< internal >
+ * desktop_file_dir_init:
+ * @dir: a #DesktopFileDir
+ *
+ * Does initial setup for @dir
+ *
+ * You should only call this if @dir is not already setup.
+ */
 static void
-desktop_file_dirs_refresh (void)
+desktop_file_dir_init (DesktopFileDir *dir)
+{
+  g_assert (!dir->is_setup);
+
+  g_assert (!dir->monitor);
+  dir->monitor = g_local_directory_monitor_new_in_worker (dir->path, G_FILE_MONITOR_NONE, NULL);
+
+  if (dir->monitor)
+    {
+      g_signal_connect (dir->monitor, "changed", G_CALLBACK (desktop_file_dir_changed), dir);
+      g_local_directory_monitor_start (dir->monitor);
+    }
+
+  desktop_file_dir_unindexed_init (dir);
+
+  dir->is_setup = TRUE;
+}
+
+/*< internal >
+ * desktop_file_dir_get_app:
+ * @dir: a DesktopFileDir
+ * @desktop_id: the desktop ID to load
+ *
+ * Creates the #GDesktopAppInfo for the given @desktop_id if it exists
+ * within @dir, even if it is hidden.
+ *
+ * This function does not check if @desktop_id would be masked by a
+ * directory with higher precedence.  The caller must do so.
+ */
+static GDesktopAppInfo *
+desktop_file_dir_get_app (DesktopFileDir *dir,
+                          const gchar    *desktop_id)
+{
+  if (!dir->app_names)
+    return NULL;
+
+  return desktop_file_dir_unindexed_get_app (dir, desktop_id);
+}
+
+/*< internal >
+ * desktop_file_dir_get_all:
+ * @dir: a DesktopFileDir
+ * @apps: a #GHashTable<string, GDesktopAppInfo>
+ *
+ * Loads all desktop files in @dir and adds them to @apps, careful to
+ * ensure we don't add any files masked by a similarly-named file in a
+ * higher-precedence directory.
+ */
+static void
+desktop_file_dir_get_all (DesktopFileDir *dir,
+                          GHashTable     *apps)
+{
+  desktop_file_dir_unindexed_get_all (dir, apps);
+}
+
+/* Lock/unlock and global setup API {{{2 */
+
+static void
+desktop_file_dirs_lock (void)
 {
-  if (g_once_init_enter (&desktop_file_dirs))
+  gint i;
+
+  g_mutex_lock (&desktop_file_dir_lock);
+
+  if (desktop_file_dirs == NULL)
     {
       const char * const *data_dirs;
       GArray *tmp;
@@ -192,10 +463,39 @@ desktop_file_dirs_refresh (void)
       for (i = 0; data_dirs[i]; i++)
         desktop_file_dir_create (tmp, data_dirs[i]);
 
+      desktop_file_dirs = (DesktopFileDir *) tmp->data;
       n_desktop_file_dirs = tmp->len;
 
-      g_once_init_leave (&desktop_file_dirs, (DesktopFileDir *) g_array_free (tmp, FALSE));
+      g_array_free (tmp, FALSE);
     }
+
+  for (i = 0; i < n_desktop_file_dirs; i++)
+    if (!desktop_file_dirs[i].is_setup)
+      desktop_file_dir_init (&desktop_file_dirs[i]);
+}
+
+static void
+desktop_file_dirs_unlock (void)
+{
+  g_mutex_unlock (&desktop_file_dir_lock);
+}
+
+static void
+desktop_file_dirs_refresh (void)
+{
+  desktop_file_dirs_lock ();
+  desktop_file_dirs_unlock ();
+}
+
+static void
+desktop_file_dirs_invalidate_user (void)
+{
+  g_mutex_lock (&desktop_file_dir_lock);
+
+  if (n_desktop_file_dirs)
+    desktop_file_dir_reset (&desktop_file_dirs[0]);
+
+  g_mutex_unlock (&desktop_file_dir_lock);
 }
 
 /* GDesktopAppInfo implementation {{{1 */
@@ -575,47 +875,24 @@ g_desktop_app_info_new_from_filename (const char *filename)
 GDesktopAppInfo *
 g_desktop_app_info_new (const char *desktop_id)
 {
-  GDesktopAppInfo *appinfo;
-  char *basename;
-  int i;
+  GDesktopAppInfo *appinfo = NULL;
+  guint i;
 
-  desktop_file_dirs_refresh ();
+  desktop_file_dirs_lock ();
 
-  basename = g_strdup (desktop_id);
-  
   for (i = 0; i < n_desktop_file_dirs; i++)
     {
-      const gchar *path = desktop_file_dirs[i].path;
-      char *filename;
-      char *p;
-
-      filename = g_build_filename (path, desktop_id, NULL);
-      appinfo = g_desktop_app_info_new_from_filename (filename);
-      g_free (filename);
-      if (appinfo != NULL)
-       goto found;
+      appinfo = desktop_file_dir_get_app (&desktop_file_dirs[i], desktop_id);
 
-      p = basename;
-      while ((p = strchr (p, '-')) != NULL)
-       {
-         *p = '/';
-
-         filename = g_build_filename (path, basename, NULL);
-         appinfo = g_desktop_app_info_new_from_filename (filename);
-         g_free (filename);
-         if (appinfo != NULL)
-           goto found;
-         *p = '-';
-         p++;
-       }
+      if (appinfo)
+        break;
     }
 
-  g_free (basename);
-  return NULL;
+  desktop_file_dirs_unlock ();
+
+  if (appinfo == NULL)
+    return NULL;
 
- found:
-  g_free (basename);
-  
   g_free (appinfo->desktop_id);
   appinfo->desktop_id = g_strdup (desktop_id);
 
@@ -2343,6 +2620,15 @@ g_desktop_app_info_ensure_saved (GDesktopAppInfo  *info,
 
   run_update_command ("update-desktop-database", "applications");
 
+  /* We just dropped a file in the user's desktop file directory.  Save
+   * the monitor the bother of having to notice it and invalidate
+   * immediately.
+   *
+   * This means that calls directly following this will be able to see
+   * the results immediately.
+   */
+  desktop_file_dirs_invalidate_user ();
+
   return TRUE;
 }
 
@@ -2762,69 +3048,6 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
   return app_info;
 }
 
-static void
-get_apps_from_dir (GHashTable *apps, 
-                   const char *dirname, 
-                   const char *prefix)
-{
-  GDir *dir;
-  const char *basename;
-  char *filename, *subprefix, *desktop_id;
-  gboolean hidden;
-  GDesktopAppInfo *appinfo;
-  
-  dir = g_dir_open (dirname, 0, NULL);
-  if (dir)
-    {
-      while ((basename = g_dir_read_name (dir)) != NULL)
-       {
-         filename = g_build_filename (dirname, basename, NULL);
-         if (g_str_has_suffix (basename, ".desktop"))
-           {
-             desktop_id = g_strconcat (prefix, basename, NULL);
-
-             /* Use _extended so we catch NULLs too (hidden) */
-             if (!g_hash_table_lookup_extended (apps, desktop_id, NULL, NULL))
-               {
-                 appinfo = g_desktop_app_info_new_from_filename (filename);
-                  hidden = FALSE;
-
-                 if (appinfo && g_desktop_app_info_get_is_hidden (appinfo))
-                   {
-                     g_object_unref (appinfo);
-                     appinfo = NULL;
-                     hidden = TRUE;
-                   }
-                                     
-                 if (appinfo || hidden)
-                   {
-                     g_hash_table_insert (apps, g_strdup (desktop_id), appinfo);
-
-                     if (appinfo)
-                       {
-                         /* Reuse instead of strdup here */
-                         appinfo->desktop_id = desktop_id;
-                         desktop_id = NULL;
-                       }
-                   }
-               }
-             g_free (desktop_id);
-           }
-         else
-           {
-             if (g_file_test (filename, G_FILE_TEST_IS_DIR))
-               {
-                 subprefix = g_strconcat (prefix, basename, "-", NULL);
-                 get_apps_from_dir (apps, filename, subprefix);
-                 g_free (subprefix);
-               }
-           }
-         g_free (filename);
-       }
-      g_dir_close (dir);
-    }
-}
-
 /* "Get all" API {{{2 */
 
 /**
@@ -2851,15 +3074,14 @@ g_app_info_get_all (void)
   int i;
   GList *infos;
 
-  desktop_file_dirs_refresh ();
-
-  apps = g_hash_table_new_full (g_str_hash, g_str_equal,
-                               g_free, NULL);
+  apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
+  desktop_file_dirs_lock ();
 
   for (i = 0; i < n_desktop_file_dirs; i++)
-    get_apps_from_dir (apps, desktop_file_dirs[i].path, "");
+    desktop_file_dir_get_all (&desktop_file_dirs[i], apps);
 
+  desktop_file_dirs_unlock ();
 
   infos = NULL;
   g_hash_table_iter_init (&iter, apps);
@@ -2871,7 +3093,7 @@ g_app_info_get_all (void)
 
   g_hash_table_destroy (apps);
 
-  return g_list_reverse (infos);
+  return infos;
 }
 
 /* Caching of mimeinfo.cache and defaults.list files {{{2 */


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