/*
* Copyright (C) 2002 - 2004 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "entry-directories.h"
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include "menu-util.h"
#include "menu-monitor.h"
typedef struct CachedDir CachedDir;
typedef struct CachedDirMonitor CachedDirMonitor;
struct EntryDirectory
{
CachedDir *dir;
guint entry_type : 2;
guint refcount : 24;
};
struct EntryDirectoryList
{
int refcount;
int length;
GList *dirs;
};
struct CachedDir
{
CachedDir *parent;
char *name;
GSList *entries;
GSList *subdirs;
GSList *retry_later_desktop_entries;
MenuMonitor *dir_monitor;
GSList *monitors;
guint have_read_entries : 1;
guint deleted : 1;
guint references;
GFunc notify;
gpointer notify_data;
};
struct CachedDirMonitor
{
EntryDirectory *ed;
EntryDirectoryChangedFunc callback;
gpointer user_data;
};
static void cached_dir_add_reference (CachedDir *dir);
static void cached_dir_remove_reference (CachedDir *dir);
static void cached_dir_free (CachedDir *dir);
static gboolean cached_dir_load_entries_recursive (CachedDir *dir,
const char *dirname);
static void cached_dir_unref (CachedDir *dir);
static void cached_dir_unref_noparent (CachedDir *dir);
static CachedDir * cached_dir_add_subdir (CachedDir *dir,
const char *basename,
const char *path);
static gboolean cached_dir_remove_subdir (CachedDir *dir,
const char *basename);
static void handle_cached_dir_changed (MenuMonitor *monitor,
MenuMonitorEvent event,
const char *path,
CachedDir *dir);
/*
* Entry directory cache
*/
static CachedDir *dir_cache = NULL;
static void
clear_cache (CachedDir *dir,
gpointer *cache)
{
*cache = NULL;
}
static CachedDir *
cached_dir_new (const char *name)
{
CachedDir *dir;
dir = g_new0 (CachedDir, 1);
dir->name = g_strdup (name);
return dir;
}
static CachedDir *
cached_dir_new_full (const char *name,
GFunc notify,
gpointer notify_data)
{
CachedDir *dir;
dir = cached_dir_new (name);
dir->notify = notify;
dir->notify_data = notify_data;
return dir;
}
static void
cached_dir_free (CachedDir *dir)
{
if (dir->dir_monitor)
{
menu_monitor_remove_notify (dir->dir_monitor,
(MenuMonitorNotifyFunc) handle_cached_dir_changed,
dir);
menu_monitor_unref (dir->dir_monitor);
dir->dir_monitor = NULL;
}
g_slist_foreach (dir->monitors, (GFunc) g_free, NULL);
g_slist_free (dir->monitors);
dir->monitors = NULL;
g_slist_foreach (dir->entries,
(GFunc) desktop_entry_unref,
NULL);
g_slist_free (dir->entries);
dir->entries = NULL;
g_slist_foreach (dir->subdirs,
(GFunc) cached_dir_unref_noparent,
NULL);
g_slist_free (dir->subdirs);
dir->subdirs = NULL;
g_slist_free_full (dir->retry_later_desktop_entries, g_free);
dir->retry_later_desktop_entries = NULL;
g_free (dir->name);
g_free (dir);
}
static CachedDir *
cached_dir_ref (CachedDir *dir)
{
dir->references++;
return dir;
}
static void
cached_dir_unref (CachedDir *dir)
{
if (--dir->references == 0)
{
CachedDir *parent;
parent = dir->parent;
if (parent != NULL)
cached_dir_remove_subdir (parent, dir->name);
if (dir->notify)
dir->notify (dir, dir->notify_data);
cached_dir_free (dir);
}
}
static void
cached_dir_unref_noparent (CachedDir *dir)
{
if (--dir->references == 0)
{
if (dir->notify)
dir->notify (dir, dir->notify_data);
cached_dir_free (dir);
}
}
static inline CachedDir *
find_subdir (CachedDir *dir,
const char *subdir)
{
GSList *tmp;
tmp = dir->subdirs;
while (tmp != NULL)
{
CachedDir *sub = tmp->data;
if (strcmp (sub->name, subdir) == 0)
return sub;
tmp = tmp->next;
}
return NULL;
}
static DesktopEntry *
find_entry (CachedDir *dir,
const char *basename)
{
GSList *tmp;
tmp = dir->entries;
while (tmp != NULL)
{
if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
return tmp->data;
tmp = tmp->next;
}
return NULL;
}
static DesktopEntry *
cached_dir_find_relative_path (CachedDir *dir,
const char *relative_path)
{
DesktopEntry *retval = NULL;
char **split;
int i;
split = g_strsplit (relative_path, "/", -1);
i = 0;
while (split[i] != NULL)
{
if (split[i + 1] != NULL)
{
if ((dir = find_subdir (dir, split[i])) == NULL)
break;
}
else
{
retval = find_entry (dir, split[i]);
break;
}
++i;
}
g_strfreev (split);
return retval;
}
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_add_entry (CachedDir *dir,
const char *basename,
const char *path)
{
DesktopEntry *entry;
DesktopEntryResultCode code;
entry = desktop_entry_new (path, &code);
if (entry == NULL)
{
if (code == DESKTOP_ENTRY_LOAD_FAIL_APPINFO)
{
menu_verbose ("Adding %s to the retry list (mimeinfo.cache maybe isn't done getting updated yet\n", path);
dir->retry_later_desktop_entries = g_slist_prepend (dir->retry_later_desktop_entries, g_strdup (path));
}
return FALSE;
}
dir->entries = g_slist_prepend (dir->entries, entry);
return TRUE;
}
static gboolean
cached_dir_update_entry (CachedDir *dir,
const char *basename,
const char *path)
{
GSList *tmp;
tmp = dir->entries;
while (tmp != NULL)
{
if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
{
if (!desktop_entry_reload (tmp->data))
{
dir->entries = g_slist_delete_link (dir->entries, tmp);
}
return TRUE;
}
tmp = tmp->next;
}
return cached_dir_add_entry (dir, basename, path);
}
static gboolean
cached_dir_remove_entry (CachedDir *dir,
const char *basename)
{
GSList *tmp;
tmp = dir->entries;
while (tmp != NULL)
{
if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
{
desktop_entry_unref (tmp->data);
dir->entries = g_slist_delete_link (dir->entries, tmp);
return TRUE;
}
tmp = tmp->next;
}
return FALSE;
}
static CachedDir *
cached_dir_add_subdir (CachedDir *dir,
const char *basename,
const char *path)
{
CachedDir *subdir;
subdir = find_subdir (dir, basename);
if (subdir != NULL)
{
subdir->deleted = FALSE;
return subdir;
}
subdir = cached_dir_new (basename);
if (path != NULL && !cached_dir_load_entries_recursive (subdir, path))
{
cached_dir_free (subdir);
return NULL;
}
menu_verbose ("Caching dir \"%s\"\n", basename);
subdir->parent = dir;
dir->subdirs = g_slist_prepend (dir->subdirs, cached_dir_ref (subdir));
return subdir;
}
static gboolean
cached_dir_remove_subdir (CachedDir *dir,
const char *basename)
{
CachedDir *subdir;
subdir = find_subdir (dir, basename);
if (subdir != NULL)
{
subdir->deleted = TRUE;
cached_dir_unref (subdir);
dir->subdirs = g_slist_remove (dir->subdirs, subdir);
return TRUE;
}
return FALSE;
}
static guint monitors_idle_handler = 0;
static GSList *pending_monitors_dirs = NULL;
static void
cached_dir_invoke_monitors (CachedDir *dir)
{
GSList *tmp;
tmp = dir->monitors;
while (tmp != NULL)
{
CachedDirMonitor *monitor = tmp->data;
GSList *next = tmp->next;
monitor->callback (monitor->ed, monitor->user_data);
tmp = next;
}
/* we explicitly don't invoke monitors of the parent since an
* event has been queued for it too */
}
static gboolean
emit_monitors_in_idle (void)
{
GSList *monitors_to_emit;
GSList *tmp;
monitors_to_emit = pending_monitors_dirs;
pending_monitors_dirs = NULL;
monitors_idle_handler = 0;
tmp = monitors_to_emit;
while (tmp != NULL)
{
CachedDir *dir = tmp->data;
cached_dir_invoke_monitors (dir);
cached_dir_remove_reference (dir);
tmp = tmp->next;
}
g_slist_free (monitors_to_emit);
return FALSE;
}
static void
cached_dir_queue_monitor_event (CachedDir *dir)
{
GSList *tmp;
tmp = pending_monitors_dirs;
while (tmp != NULL)
{
CachedDir *d = tmp->data;
GSList *next = tmp->next;
if (dir->parent == d->parent &&
g_strcmp0 (dir->name, d->name) == 0)
break;
tmp = next;
}
/* not found, so let's queue it */
if (tmp == NULL)
{
cached_dir_add_reference (dir);
pending_monitors_dirs = g_slist_append (pending_monitors_dirs, dir);
}
if (dir->parent)
{
cached_dir_queue_monitor_event (dir->parent);
}
if (monitors_idle_handler > 0)
{
g_source_remove (monitors_idle_handler);
}
monitors_idle_handler = g_timeout_add (100, (GSourceFunc) emit_monitors_in_idle, NULL);
}
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 (gmenu_desktopappinfo_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 gmenu_desktopappinfo_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);
}
static void
cached_dir_ensure_monitor (CachedDir *dir,
const char *dirname)
{
if (dir->dir_monitor == NULL)
{
dir->dir_monitor = menu_get_directory_monitor (dirname);
menu_monitor_add_notify (dir->dir_monitor,
(MenuMonitorNotifyFunc) handle_cached_dir_changed,
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
cached_dir_add_monitor (CachedDir *dir,
EntryDirectory *ed,
EntryDirectoryChangedFunc callback,
gpointer user_data)
{
CachedDirMonitor *monitor;
GSList *tmp;
tmp = dir->monitors;
while (tmp != NULL)
{
monitor = tmp->data;
if (monitor->ed == ed &&
monitor->callback == callback &&
monitor->user_data == user_data)
break;
tmp = tmp->next;
}
if (tmp == NULL)
{
monitor = g_new0 (CachedDirMonitor, 1);
monitor->ed = ed;
monitor->callback = callback;
monitor->user_data = user_data;
dir->monitors = g_slist_append (dir->monitors, monitor);
}
}
static void
cached_dir_remove_monitor (CachedDir *dir,
EntryDirectory *ed,
EntryDirectoryChangedFunc callback,
gpointer user_data)
{
GSList *tmp;
tmp = dir->monitors;
while (tmp != NULL)
{
CachedDirMonitor *monitor = tmp->data;
GSList *next = tmp->next;
if (monitor->ed == ed &&
monitor->callback == callback &&
monitor->user_data == user_data)
{
dir->monitors = g_slist_delete_link (dir->monitors, tmp);
g_free (monitor);
}
tmp = next;
}
}
static void
cached_dir_add_reference (CachedDir *dir)
{
cached_dir_ref (dir);
if (dir->parent != NULL)
{
cached_dir_add_reference (dir->parent);
}
}
static void
cached_dir_remove_reference (CachedDir *dir)
{
CachedDir *parent;
parent = dir->parent;
cached_dir_unref (dir);
if (parent != NULL)
{
cached_dir_remove_reference (parent);
}
}
/*
* Entry directories
*/
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;
}
EntryDirectory *
entry_directory_ref (EntryDirectory *ed)
{
g_return_val_if_fail (ed != NULL, NULL);
g_return_val_if_fail (ed->refcount > 0, NULL);
ed->refcount++;
return ed;
}
void
entry_directory_unref (EntryDirectory *ed)
{
g_return_if_fail (ed != NULL);
g_return_if_fail (ed->refcount > 0);
if (--ed->refcount == 0)
{
cached_dir_remove_reference (ed->dir);
ed->dir = NULL;
ed->entry_type = DESKTOP_ENTRY_INVALID;
g_free (ed);
}
}
static void
entry_directory_add_monitor (EntryDirectory *ed,
EntryDirectoryChangedFunc callback,
gpointer user_data)
{
cached_dir_add_monitor (ed->dir, ed, callback, user_data);
}
static void
entry_directory_remove_monitor (EntryDirectory *ed,
EntryDirectoryChangedFunc callback,
gpointer user_data)
{
cached_dir_remove_monitor (ed->dir, ed, callback, user_data);
}
static DesktopEntry *
entry_directory_get_directory (EntryDirectory *ed,
const char *relative_path)
{
DesktopEntry *entry;
if (ed->entry_type != DESKTOP_ENTRY_DIRECTORY)
return NULL;
entry = cached_dir_find_relative_path (ed->dir, relative_path);
if (entry == NULL || desktop_entry_get_type (entry) != DESKTOP_ENTRY_DIRECTORY)
return NULL;
return desktop_entry_ref (entry);
}
static char *
get_desktop_file_id_from_path (EntryDirectory *ed,
DesktopEntryType entry_type,
const char *relative_path,
DesktopEntry *entry)
{
char *retval;
retval = NULL;
if (entry_type == DESKTOP_ENTRY_DESKTOP)
{
GMenuDesktopAppInfo *appinfo;
appinfo = desktop_entry_get_app_info (entry);
retval = g_strdelimit (g_strdup (relative_path), "/", '-');
if (gmenu_desktopappinfo_get_is_flatpak (appinfo))
{
char* tmp;
tmp = retval;
retval = g_strconcat (retval, GMENU_DESKTOPAPPINFO_FLATPAK_SUFFIX, NULL);
g_free (tmp);
}
}
else
{
retval = g_strdup (relative_path);
}
return retval;
}
typedef gboolean (* EntryDirectoryForeachFunc) (EntryDirectory *ed,
DesktopEntry *entry,
const char *file_id,
DesktopEntrySet *set,
gpointer user_data);
static gboolean
entry_directory_foreach_recursive (EntryDirectory *ed,
CachedDir *cd,
GString *relative_path,
EntryDirectoryForeachFunc func,
DesktopEntrySet *set,
gpointer user_data)
{
GSList *tmp;
int relative_path_len;
if (cd->deleted)
return TRUE;
relative_path_len = relative_path->len;
tmp = cd->entries;
while (tmp != NULL)
{
DesktopEntry *entry = tmp->data;
if (desktop_entry_get_type (entry) == ed->entry_type)
{
gboolean ret;
char *file_id;
g_string_append (relative_path,
desktop_entry_get_basename (entry));
file_id = get_desktop_file_id_from_path (ed,
ed->entry_type,
relative_path->str,
entry);
ret = func (ed, entry, file_id, set, user_data);
g_free (file_id);
g_string_truncate (relative_path, relative_path_len);
if (!ret)
return FALSE;
}
tmp = tmp->next;
}
tmp = cd->subdirs;
while (tmp != NULL)
{
CachedDir *subdir = tmp->data;
g_string_append (relative_path, subdir->name);
g_string_append_c (relative_path, G_DIR_SEPARATOR);
if (!entry_directory_foreach_recursive (ed,
subdir,
relative_path,
func,
set,
user_data))
return FALSE;
g_string_truncate (relative_path, relative_path_len);
tmp = tmp->next;
}
return TRUE;
}
static void
entry_directory_foreach (EntryDirectory *ed,
EntryDirectoryForeachFunc func,
DesktopEntrySet *set,
gpointer user_data)
{
GString *path;
path = g_string_new (NULL);
entry_directory_foreach_recursive (ed,
ed->dir,
path,
func,
set,
user_data);
g_string_free (path, TRUE);
}
void
entry_directory_get_flat_contents (EntryDirectory *ed,
DesktopEntrySet *desktop_entries,
DesktopEntrySet *directory_entries,
GSList **subdirs)
{
GSList *tmp;
if (subdirs)
*subdirs = NULL;
tmp = ed->dir->entries;
while (tmp != NULL)
{
DesktopEntry *entry = tmp->data;
const char *basename;
basename = desktop_entry_get_basename (entry);
if (desktop_entries &&
desktop_entry_get_type (entry) == DESKTOP_ENTRY_DESKTOP)
{
desktop_entry_set_add_entry (desktop_entries,
entry,
NULL);
}
if (directory_entries &&
desktop_entry_get_type (entry) == DESKTOP_ENTRY_DIRECTORY)
{
desktop_entry_set_add_entry (directory_entries,
entry,
basename);
}
tmp = tmp->next;
}
if (subdirs)
{
tmp = ed->dir->subdirs;
while (tmp != NULL)
{
CachedDir *cd = tmp->data;
if (!cd->deleted)
{
*subdirs = g_slist_prepend (*subdirs, g_strdup (cd->name));
}
tmp = tmp->next;
}
}
if (subdirs)
*subdirs = g_slist_reverse (*subdirs);
}
/*
* Entry directory lists
*/
EntryDirectoryList *
entry_directory_list_new (void)
{
EntryDirectoryList *list;
list = g_new0 (EntryDirectoryList, 1);
list->refcount = 1;
list->dirs = NULL;
list->length = 0;
return list;
}
EntryDirectoryList *
entry_directory_list_ref (EntryDirectoryList *list)
{
g_return_val_if_fail (list != NULL, NULL);
g_return_val_if_fail (list->refcount > 0, NULL);
list->refcount += 1;
return list;
}
void
entry_directory_list_unref (EntryDirectoryList *list)
{
g_return_if_fail (list != NULL);
g_return_if_fail (list->refcount > 0);
list->refcount -= 1;
if (list->refcount == 0)
{
g_list_foreach (list->dirs, (GFunc) entry_directory_unref, NULL);
g_list_free (list->dirs);
list->dirs = NULL;
list->length = 0;
g_free (list);
}
}
void
entry_directory_list_prepend (EntryDirectoryList *list,
EntryDirectory *ed)
{
list->length += 1;
list->dirs = g_list_prepend (list->dirs,
entry_directory_ref (ed));
}
int
entry_directory_list_get_length (EntryDirectoryList *list)
{
return list->length;
}
void
entry_directory_list_append_list (EntryDirectoryList *list,
EntryDirectoryList *to_append)
{
GList *tmp;
GList *new_dirs = NULL;
if (to_append->length == 0)
return;
tmp = to_append->dirs;
while (tmp != NULL)
{
list->length += 1;
new_dirs = g_list_prepend (new_dirs,
entry_directory_ref (tmp->data));
tmp = tmp->next;
}
new_dirs = g_list_reverse (new_dirs);
list->dirs = g_list_concat (list->dirs, new_dirs);
}
DesktopEntry *
entry_directory_list_get_directory (EntryDirectoryList *list,
const char *relative_path)
{
DesktopEntry *retval = NULL;
GList *tmp;
tmp = list->dirs;
while (tmp != NULL)
{
if ((retval = entry_directory_get_directory (tmp->data, relative_path)) != NULL)
break;
tmp = tmp->next;
}
return retval;
}
gboolean
_entry_directory_list_compare (const EntryDirectoryList *a,
const EntryDirectoryList *b)
{
GList *al, *bl;
if (a == NULL && b == NULL)
return TRUE;
if ((a == NULL || b == NULL))
return FALSE;
if (a->length != b->length)
return FALSE;
al = a->dirs; bl = b->dirs;
while (al && bl && al->data == bl->data)
{
al = al->next;
bl = bl->next;
}
return (al == NULL && bl == NULL);
}
static gboolean
get_all_func (EntryDirectory *ed,
DesktopEntry *entry,
const char *file_id,
DesktopEntrySet *set,
gpointer user_data)
{
entry = desktop_entry_ref (entry);
desktop_entry_set_add_entry (set, entry, file_id);
desktop_entry_unref (entry);
return TRUE;
}
static DesktopEntrySet *entry_directory_last_set = NULL;
static EntryDirectoryList *entry_directory_last_list = NULL;
void
_entry_directory_list_empty_desktop_cache (void)
{
if (entry_directory_last_set != NULL)
desktop_entry_set_unref (entry_directory_last_set);
entry_directory_last_set = NULL;
if (entry_directory_last_list != NULL)
entry_directory_list_unref (entry_directory_last_list);
entry_directory_last_list = NULL;
}
DesktopEntrySet *
_entry_directory_list_get_all_desktops (EntryDirectoryList *list)
{
GList *tmp;
DesktopEntrySet *set;
/* The only tricky thing here is that desktop files later
* in the search list with the same relative path
* are "hidden" by desktop files earlier in the path,
* so we have to do the earlier files first causing
* the later files to replace the earlier files
* in the DesktopEntrySet
*
* We go from the end of the list so we can just
* g_hash_table_replace and not have to do two
* hash lookups (check for existing entry, then insert new
* entry)
*/
/* This method is -extremely- slow, so we have a simple
one-entry cache here */
if (_entry_directory_list_compare (list, entry_directory_last_list))
{
menu_verbose (" Hit desktop list (%p) cache\n", list);
return desktop_entry_set_ref (entry_directory_last_set);
}
if (entry_directory_last_set != NULL)
desktop_entry_set_unref (entry_directory_last_set);
if (entry_directory_last_list != NULL)
entry_directory_list_unref (entry_directory_last_list);
set = desktop_entry_set_new ();
menu_verbose (" Storing all of list %p in set %p\n",
list, set);
tmp = g_list_last (list->dirs);
while (tmp != NULL)
{
entry_directory_foreach (tmp->data, get_all_func, set, NULL);
tmp = tmp->prev;
}
entry_directory_last_list = entry_directory_list_ref (list);
entry_directory_last_set = desktop_entry_set_ref (set);
return set;
}
void
entry_directory_list_add_monitors (EntryDirectoryList *list,
EntryDirectoryChangedFunc callback,
gpointer user_data)
{
GList *tmp;
tmp = list->dirs;
while (tmp != NULL)
{
entry_directory_add_monitor (tmp->data, callback, user_data);
tmp = tmp->next;
}
}
void
entry_directory_list_remove_monitors (EntryDirectoryList *list,
EntryDirectoryChangedFunc callback,
gpointer user_data)
{
GList *tmp;
tmp = list->dirs;
while (tmp != NULL)
{
entry_directory_remove_monitor (tmp->data, callback, user_data);
tmp = tmp->next;
}
}