diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 141a89aa2..c67186fe3 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_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 + * + * 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_refresh (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; + appinfo = desktop_file_dir_get_app (&desktop_file_dirs[i], desktop_id); - 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; - - 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 */