#include "glibintl.h"
#include "giomodule-priv.h"
#include "gappinfo.h"
+#include "glocaldirectorymonitor.h"
/**
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 */
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;
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 */
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);
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;
}
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 */
/**
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);
g_hash_table_destroy (apps);
- return g_list_reverse (infos);
+ return infos;
}
/* Caching of mimeinfo.cache and defaults.list files {{{2 */