static CachedDir *
cached_dir_lookup (const char *canonical)
{
  CachedDir  *dir;
  char      **split;
  int         i;

  if (dir_cache == NULL)
    dir_cache = cached_dir_new_full ("/",
                                     (GFunc) clear_cache,
                                     &dir_cache);
  dir = dir_cache;

  g_assert (canonical != NULL && canonical[0] == G_DIR_SEPARATOR);

  menu_verbose ("Looking up cached dir \"%s\"\n", canonical);

  split = g_strsplit (canonical + 1, "/", -1);

  i = 0;
  while (split[i] != NULL)
    {
      CachedDir *subdir;

      subdir = cached_dir_add_subdir (dir, split[i], NULL);

      dir = subdir;

      ++i;
    }

  g_strfreev (split);

  g_assert (dir != NULL);

  return dir;
}
static gboolean
cached_dir_load_entries_recursive (CachedDir  *dir,
                                   const char *dirname)
{
  DIR           *dp;
  struct dirent *dent;
  GString       *fullpath;
  gsize          fullpath_len;

  g_assert (dir != NULL);

  if (dir->have_read_entries)
    return TRUE;

  menu_verbose ("Attempting to read entries from %s (full path %s)\n",
                dir->name, dirname);

  dp = opendir (dirname);
  if (dp == NULL)
    {
      menu_verbose ("Unable to list directory \"%s\"\n",
                    dirname);
      return FALSE;
    }

  cached_dir_ensure_monitor (dir, dirname);

  fullpath = g_string_new (dirname);
  if (fullpath->str[fullpath->len - 1] != G_DIR_SEPARATOR)
    g_string_append_c (fullpath, G_DIR_SEPARATOR);

  fullpath_len = fullpath->len;

  while ((dent = readdir (dp)) != NULL)
    {
      /* ignore . and .. */
      if (dent->d_name[0] == '.' &&
          (dent->d_name[1] == '\0' ||
           (dent->d_name[1] == '.' &&
            dent->d_name[2] == '\0')))
        continue;

      g_string_append (fullpath, dent->d_name);

      if (g_str_has_suffix (dent->d_name, ".desktop") ||
          g_str_has_suffix (dent->d_name, ".directory"))
        {
          cached_dir_add_entry (dir, dent->d_name, fullpath->str);
        }
      else /* Try recursing */
        {
          cached_dir_add_subdir (dir, dent->d_name, fullpath->str);
        }

      g_string_truncate (fullpath, fullpath_len);
    }

  closedir (dp);

  g_string_free (fullpath, TRUE);

  dir->have_read_entries = TRUE;

  return TRUE;
}
static void
handle_cached_dir_changed (MenuMonitor      *monitor,
			   MenuMonitorEvent  event,
			   const char       *path,
                           CachedDir        *dir)
{
  gboolean  handled = FALSE;
  char     *basename;
  char     *dirname;

  menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n",
		dir->name,
                path,
                event == MENU_MONITOR_EVENT_CREATED ? ("created") :
                event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed"));

  dirname  = g_path_get_dirname  (path);
  basename = g_path_get_basename (path);

  dir = cached_dir_lookup (dirname);

  if (g_str_has_suffix (basename, ".desktop") ||
      g_str_has_suffix (basename, ".directory"))
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
        case MENU_MONITOR_EVENT_CHANGED:
          handled = cached_dir_update_entry (dir, basename, path);
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_entry (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }
  else /* Try recursing */
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
          handled = cached_dir_add_subdir (dir, basename, path) != NULL;
          break;

        case MENU_MONITOR_EVENT_CHANGED:
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_subdir (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }

  g_free (basename);
  g_free (dirname);

  if (handled)
    {
      /* CHANGED events don't change the set of desktop entries */
      if (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED)
        {
          _entry_directory_list_empty_desktop_cache ();
        }

      cached_dir_queue_monitor_event (dir);
    }
}
static void
handle_cached_dir_changed (MenuMonitor      *monitor,
			   MenuMonitorEvent  event,
			   const char       *path,
                           CachedDir        *dir)
{
  gboolean  handled = FALSE;
  gboolean  retry_changes = FALSE;

  char     *basename;
  char     *dirname;

  dirname  = g_path_get_dirname  (path);
  basename = g_path_get_basename (path);

  dir = cached_dir_lookup (dirname);
  cached_dir_add_reference (dir);

  if (g_str_has_suffix (basename, ".desktop") ||
      g_str_has_suffix (basename, ".directory"))
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
        case MENU_MONITOR_EVENT_CHANGED:
          handled = cached_dir_update_entry (dir, basename, path);
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_entry (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }
  else if (g_strcmp0 (basename, "mimeinfo.cache") == 0)
    {
        /* The observed file notifies when a new desktop file is added
         * (but fails to load) go something like:
         *
         * NOTIFY: foo.desktop
         * NOTIFY: mimeinfo.cache.tempfile
         * NOTIFY: mimeinfo.cache.tempfile
         * NOTIFY: mimeinfo.cache
         *
         * Additionally, the failure is not upon trying to read the file,
         * but attempting to get its GAppInfo (g_desktop_app_info_new_from_filename()
         * in desktop-entries.c ln 277).  If you jigger desktop_entry_load() around
         * and read the file as a keyfile *first*, it succeeds.  If you then try
         * to run g_desktop_app_info_new_from_keyfile(), *then* it fails.
         *
         * The theory here is there is a race condition where app info (which includes
         * mimetype stuff) is unavailable because mimeinfo.cache is updated immediately
         * after the app is installed.
         *
         * What we do here is, when a desktop fails to load, we add it to a temporary
         * list.  We wait until mimeinfo.cache changes, then retry that desktop file,
         * which succeeds this second time.
         *
         * Note: An alternative fix (presented more as a proof than a suggestion) is to
         * change line 151 in menu-monitor.c to use g_timeout_add_seconds, and delay
         * for one second.  This also avoids the issue (but it remains a race condition).
         */

        GSList *iter;

        menu_verbose ("mimeinfo changed, checking for failed entries\n");

        for (iter = dir->retry_later_desktop_entries; iter != NULL; iter = iter->next)
          {
            const gchar *retry_path = iter->data;

            menu_verbose ("retrying %s\n", retry_path);

            char *retry_basename = g_path_get_basename (retry_path);

            if (cached_dir_update_entry (dir, retry_basename, retry_path))
              retry_changes = TRUE;

            g_free (retry_basename);
          }

        g_slist_free_full (dir->retry_later_desktop_entries, g_free);
        dir->retry_later_desktop_entries = NULL;

        handled = retry_changes;
    }
  else /* Try recursing */
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
          handled = cached_dir_add_subdir (dir, basename, path) != NULL;
          break;

        case MENU_MONITOR_EVENT_CHANGED:
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_subdir (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }

  g_free (basename);
  g_free (dirname);

  if (handled)
    {
      menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n",
                    dir->name,
                    path,
                    event == MENU_MONITOR_EVENT_CREATED ? ("created") :
                    event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed"));

      /* CHANGED events don't change the set of desktop entries, unless it's the mimeinfo.cache file changing */
      if (retry_changes || (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED))
        {
          _entry_directory_list_empty_desktop_cache ();
        }

      cached_dir_queue_monitor_event (dir);
    }

  cached_dir_remove_reference (dir);
}