typedef struct
{
gchar *path;
+ gchar *alternatively_watching;
gboolean is_config;
gboolean is_setup;
GLocalDirectoryMonitor *monitor;
/* Monitor 'changed' signal handler {{{2 */
static void desktop_file_dir_reset (DesktopFileDir *dir);
+/*< internal >
+ * desktop_file_dir_get_alternative_dir:
+ * @dir: a #DesktopFileDir
+ *
+ * Gets the "alternative" directory to monitor in case the path
+ * doesn't exist.
+ *
+ * If the path exists this will return NULL, otherwise it will return a
+ * parent directory of the path.
+ *
+ * This is used to avoid inotify on a non-existent directory (which
+ * results in polling).
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=522314 for more info.
+ */
+static gchar *
+desktop_file_dir_get_alternative_dir (DesktopFileDir *dir)
+{
+ gchar *parent;
+
+ /* If the directory itself exists then we need no alternative. */
+ if (g_access (dir->path, R_OK | X_OK) == 0)
+ return NULL;
+
+ /* Otherwise, try the parent directories until we find one. */
+ parent = g_path_get_dirname (dir->path);
+
+ while (g_access (parent, R_OK | X_OK) != 0)
+ {
+ gchar *tmp = parent;
+
+ parent = g_path_get_dirname (tmp);
+
+ /* If somehow we get to '/' or '.' then just stop... */
+ if (g_str_equal (parent, tmp))
+ {
+ g_free (tmp);
+ break;
+ }
+
+ g_free (tmp);
+ }
+
+ return parent;
+}
+
static void
desktop_file_dir_changed (GFileMonitor *monitor,
GFile *file,
gpointer user_data)
{
DesktopFileDir *dir = user_data;
+ gboolean do_nothing = FALSE;
/* 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.
+ *
+ * If this is a notification for a parent directory (because the
+ * desktop directory didn't exist) then we shouldn't fire the signal
+ * unless something actually changed.
*/
g_mutex_lock (&desktop_file_dir_lock);
- desktop_file_dir_reset (dir);
+ if (dir->alternatively_watching)
+ {
+ gchar *alternative_dir;
+
+ alternative_dir = desktop_file_dir_get_alternative_dir (dir);
+ do_nothing = alternative_dir && g_str_equal (dir->alternatively_watching, alternative_dir);
+ g_free (alternative_dir);
+ }
+
+ if (!do_nothing)
+ desktop_file_dir_reset (dir);
g_mutex_unlock (&desktop_file_dir_lock);
/* Notify anyone else who may be interested */
- g_app_info_monitor_fire ();
+ if (!do_nothing)
+ g_app_info_monitor_fire ();
}
/* Internal utility functions {{{2 */
static void
desktop_file_dir_reset (DesktopFileDir *dir)
{
+ if (dir->alternatively_watching)
+ {
+ g_free (dir->alternatively_watching);
+ dir->alternatively_watching = NULL;
+ }
+
if (dir->monitor)
{
g_signal_handlers_disconnect_by_func (dir->monitor, desktop_file_dir_changed, dir);
static void
desktop_file_dir_init (DesktopFileDir *dir)
{
+ const gchar *watch_dir;
+
g_assert (!dir->is_setup);
+ g_assert (!dir->alternatively_watching);
g_assert (!dir->monitor);
- dir->monitor = g_local_directory_monitor_new_in_worker (dir->path, G_FILE_MONITOR_NONE, NULL);
+
+ dir->alternatively_watching = desktop_file_dir_get_alternative_dir (dir);
+ watch_dir = dir->alternatively_watching ? dir->alternatively_watching : dir->path;
+
+ /* There is a very thin race here if the watch_dir has been _removed_
+ * between when we checked for it and when we establish the watch.
+ * Removes probably don't happen in usual operation, and even if it
+ * does (and we catch the unlikely race), the only degradation is that
+ * we will fall back to polling.
+ */
+ dir->monitor = g_local_directory_monitor_new_in_worker (watch_dir, G_FILE_MONITOR_NONE, NULL);
if (dir->monitor)
{