EntryDirectory *
entry_directory_new (DesktopEntryType  entry_type,
                     const char       *path)
{
  EntryDirectory *ed;
  char           *canonical;

  menu_verbose ("Loading entry directory \"%s\"\n", path);

  canonical = realpath (path, NULL);
  if (canonical == NULL)
    {
      menu_verbose ("Failed to canonicalize \"%s\": %s\n",
                    path, g_strerror (errno));
      return NULL;
    }

  ed = g_new0 (EntryDirectory, 1);

  ed->dir = cached_dir_lookup (canonical);
  g_assert (ed->dir != NULL);

  cached_dir_add_reference (ed->dir);
  cached_dir_load_entries_recursive (ed->dir, canonical);

  ed->entry_type    = entry_type;
  ed->refcount      = 1;

  g_free (canonical);

  return ed;
}
static EntryDirectory *
entry_directory_new_full (DesktopEntryType  entry_type,
                          const char       *path,
                          gboolean          is_legacy,
                          const char       *legacy_prefix)
{
  EntryDirectory *ed;
  char           *canonical;

  menu_verbose ("Loading entry directory \"%s\" (legacy %s)\n",
                path,
                is_legacy ? "<yes>" : "<no>");

  canonical = menu_canonicalize_file_name (path, FALSE);
  if (canonical == NULL)
    {
      menu_verbose ("Failed to canonicalize \"%s\": %s\n",
                    path, g_strerror (errno));
      return NULL;
    }

  ed = g_new0 (EntryDirectory, 1);

  ed->dir = cached_dir_lookup (canonical);
  g_assert (ed->dir != NULL);

  cached_dir_add_reference (ed->dir);
  cached_dir_load_entries_recursive (ed->dir, canonical);

  ed->legacy_prefix = g_strdup (legacy_prefix);
  ed->entry_type    = entry_type;
  ed->is_legacy     = is_legacy != FALSE;
  ed->refcount      = 1;

  g_free (canonical);

  return ed;
}
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);
}