Re: .desktop parser, and xdg basedir api



Hi,

Interesting points. I think you are right. Those two use cases are different enough that it makes sense to me for the parser to have options for specifying
its behavior.

Okay, I've added a flags variable, that lets you specify whether to retain
comments and translations.  I've also added a function _keep_locales() that
lets the user specify a list of locales to retain translations for
(assuming KEEP_TRANSLATIONS is set). One other thing I did was make a GENERATE_LOOKUP_MAP flag for optionally keeping
a keyed data list around for faster reads.

- get_string and get_locale_string should automatically unescape key value's

I've added a _get_value() function to get the raw value and _get_string()
now unescapes the data.

  - a new function should be provided to get the escaped key values

I haven't done this yet.

  - the iconthemeparser has serialization support which my code needs

I've added a _to_data () function that outputs a desktop file

  VFS, etc, and will need to feed it data.  gmarkup.c has some examples
  of code you can look at to see how to do this.
* Loading a file isn't the right API.  We need to use this from GNOME
Oh I see.  Okay, I'll make it support incremental loading and provide a
_from_file convenience function on top.

I've added two new functions, parse_data and flush_parse_buffer.

Basically you can called parse_data in loop until you are done and each
time a new line is added it parses the line.  flush_parse_buffer is for
parsing the last line of data (in the event it doesn't end in newline)

I'm also going to rename the data type to GDesktopEntry (as suggested by
James Henstridge) since the code does more than parse.

I decided on GDesktopEntries, because it makes more sense (the spec calls
each key/value pair in the file an entry).

I still need to update the docs, add a little more error checking, and
test the code (it's mostly untested so probably has some bugs).  When
I've done those things I'll attach an updated patch to bugzilla.

In the mean time, i've attached what i've done so far.

I'd appreciate any ideas on the API and/or comments on the implementation.

(note this patch includes the patch for the xdg base dir stuff)

--Ray
? foo.def
? foo.diff
? foo.patch
? foobar.diff
Index: Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/glib/Makefile.am,v
retrieving revision 1.113
diff -p -u -u -r1.113 Makefile.am
--- Makefile.am	6 Mar 2004 07:37:40 -0000	1.113
+++ Makefile.am	29 Apr 2004 22:13:03 -0000
@@ -50,6 +50,7 @@ libglib_2_0_la_SOURCES = 	\
 	gconvert.c		\
 	gdataset.c		\
 	gdate.c         	\
+	gdesktopentries.c	\
 	gdir.c			\
 	gerror.c		\
 	gfileutils.c		\
@@ -118,6 +119,7 @@ glibsubinclude_HEADERS =   \
 	gconvert.h	\
 	gdataset.h	\
 	gdate.h		\
+	gdesktopentries.h	\
 	gdir.h		\
 	gerror.h	\
 	gfileutils.h	\
Index: gdesktopentries.c
===================================================================
RCS file: gdesktopentries.c
diff -N gdesktopentries.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gdesktopentries.c	29 Apr 2004 22:13:04 -0000
@@ -0,0 +1,2034 @@
+/* gdesktopentries.c - desktop entries parser
+ *
+ *  Copyright 2004  Ray Strode <halfline hawaii rr com>
+ *
+ * GLib 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.
+ *
+ * GLib 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 GLib; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <locale.h>
+
+#include "glib.h"
+
+#include "glibintl.h"
+#include "gutils.h"
+#include "gstring.h"
+
+typedef struct _GDesktopEntriesGroup GDesktopEntriesGroup;
+
+typedef enum
+{
+  G_DESKTOP_ENTRIES_ENCODING_GUESS = 0,
+  G_DESKTOP_ENTRIES_ENCODING_UTF8,
+  G_DESKTOP_ENTRIES_ENCODING_MIXED
+} GDesktopEntriesEncoding;
+
+struct _GDesktopEntries
+{
+  GList *groups;
+  gchar **locales;
+
+  GDesktopEntriesGroup *current_group;
+
+  GString *parse_buffer;
+
+  gboolean magic_check_is_done;
+
+  GDesktopEntriesEncoding encoding;
+  GDesktopEntriesFlags flags;
+
+  gsize approximate_size;
+};
+
+struct _GDesktopEntriesGroup
+{
+  const char *name;  /* NULL for above first group (which will be comments) */
+  GList *entries;
+
+  /* Used only for increased lookup performance
+   */
+  GData *lookup_map;
+};
+
+typedef struct 
+{
+  char *key;  /* NULL for comments */
+  char *value;
+} GDesktopEntry;
+
+
+static GDesktopEntriesGroup *g_desktop_entries_lookup_group (GDesktopEntries *entries, 
+                                                             const gchar      *group_name);
+static GDesktopEntry        *g_desktop_entries_lookup_entry (GDesktopEntries       *entries,
+		 		                             GDesktopEntriesGroup  *group,
+				                             const gchar           *key);
+
+static gchar    *g_desktop_entries_get_locale_modifier   (const gchar *locale);
+static gchar    *g_desktop_entries_get_locale_country    (const gchar *locale);
+static gchar    *g_desktop_entries_get_locale_lang       (const gchar *locale);
+static gchar    *g_desktop_entries_get_locale_encoding   (const gchar *locale);
+static gchar    *g_desktop_entries_get_fallback_encoding (const gchar *locale);
+
+static gboolean  line_is_comment (const gchar *line);
+static gboolean  line_is_group   (const gchar *line);
+static gboolean  line_is_entry   (const gchar *line);
+
+static gchar    *g_desktop_entries_key_to_utf8 (GDesktopEntries *entries,
+					        const gchar     *key,
+					        const gchar     *value);
+static gchar    *g_desktop_entries_parse_value_as_string (GDesktopEntries  *entries,
+					                  const gchar      *value,
+					                   GError          **error);
+
+static gint      g_desktop_entries_parse_value_as_integer (GDesktopEntries  *entries,
+						         const gchar    *value,
+						         GError        **error);
+static gboolean  g_desktop_entries_parse_value_as_boolean (GDesktopEntries  *entries,
+							 const gchar    *value,
+							 GError        **error);
+
+static void       g_desktop_entries_parse_comment (GDesktopEntries  *entries,
+  	                                           const char       *line,
+			                           gsize             length,
+					           GError          **error);
+static void      g_desktop_entries_parse_group (GDesktopEntries  *entries,
+	                                        const char       *line,
+			                        gsize             length,
+					        GError          **error);
+static void      g_desktop_entries_parse_entry (GDesktopEntries  *entries,
+	                                       const gchar       *line,
+			                       gsize             length,
+			                       GError          **error);
+
+static gchar    *key_get_locale (const gchar *key);
+
+GQuark
+g_desktop_entries_error_quark (void)
+{
+  static GQuark error_quark = 0;
+
+  if (error_quark == 0)
+    error_quark = g_quark_from_static_string ("g-desktop-entries-error-quark");
+
+  return error_quark;
+}
+
+GDesktopEntries *
+g_desktop_entries_new (GDesktopEntriesFlags flags)
+{
+  GDesktopEntries *entries;
+
+  entries = g_new (GDesktopEntries, 1);
+
+  entries->current_group = g_new0 (GDesktopEntriesGroup, 1);
+  entries->groups = g_list_prepend (NULL, entries->current_group);
+  entries->locales = NULL;
+  entries->parse_buffer = g_string_sized_new (128);
+  entries->magic_check_is_done = FALSE;
+  entries->encoding = G_DESKTOP_ENTRIES_ENCODING_GUESS;
+  entries->flags = flags;
+  entries->approximate_size = 0;
+
+  if (!(entries->flags & G_DESKTOP_ENTRIES_KEEP_TRANSLATIONS))
+    {
+      gchar *locales[] = { NULL };
+
+      g_desktop_entries_keep_locales (entries, locales);
+    }
+
+  return entries;
+}
+
+GDesktopEntries *
+g_desktop_entries_new_from_file (GDesktopEntriesFlags   flags,
+  	                         const gchar           *file,
+                                 GError               **error)
+{
+  GDesktopEntries *entries;
+  GError *entries_error;
+  char *contents;
+  gsize length;
+  gboolean file_loaded;
+  
+  entries_error = NULL;
+
+  file_loaded = g_file_get_contents (file, &contents, &length, &entries_error);
+
+  if (!file_loaded)
+    {
+      gchar *absolute_path;
+
+      if (!g_path_is_absolute (file))
+	{
+	  /*
+	   * FIXME: need to loop
+	   */
+	  absolute_path = g_find_file_in_data_dir (file,
+						   G_FILE_TEST_IS_REGULAR |
+						   G_FILE_TEST_IS_READABLE);
+
+	  if (absolute_path)
+	    {
+	      g_error_free (entries_error);
+	      entries_error = NULL;
+
+	      file_loaded = g_file_get_contents (absolute_path,
+						 &contents, &length,
+						 &entries_error);
+
+	      g_free (absolute_path);
+	    }
+	}
+
+      if (!file_loaded)
+	{
+	  g_propagate_error (error, entries_error);
+	  return NULL;
+	}
+    }
+
+  if (length == 0 || contents == NULL) 
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		   G_DESKTOP_ENTRIES_ERROR_PARSE,
+		   _("file is empty"));
+      return NULL;
+    }
+
+  entries = g_desktop_entries_new (flags);
+
+  entries_error = NULL;
+  g_desktop_entries_parse_data (entries, contents, length, &entries_error);
+  g_free (contents);
+
+  if (entries_error) 
+    {
+      g_propagate_error (error, entries_error);
+      g_desktop_entries_free (entries);
+
+      return NULL;
+
+    }
+
+  g_desktop_entries_flush_parse_buffer (entries, &entries_error);
+
+  if (entries_error) 
+    {
+      g_propagate_error (error, entries_error);
+      g_desktop_entries_free (entries);
+
+      return NULL;
+    }
+
+  return entries;
+}
+
+/**
+ * g_desktop_entries_free: 
+ * @entries: a #GDesktopEntries
+ * 
+ * Frees a #GDesktopEntries. 
+ **/
+void
+g_desktop_entries_free (GDesktopEntries *entries)
+{
+  GList *tmp;
+
+  g_return_if_fail (entries != NULL);
+
+  if (entries->parse_buffer)
+    g_string_free (entries->parse_buffer, TRUE);
+
+  tmp = entries->groups;
+  while (tmp != NULL)
+    {
+      GDesktopEntriesGroup *group;
+
+      group = (GDesktopEntriesGroup *) tmp->data;
+
+      tmp = tmp->next;
+
+      g_desktop_entries_remove_group (entries, group->name);
+    }
+
+  g_assert (entries->groups == NULL);
+
+  if (entries->locales != NULL)
+    g_strfreev (entries->locales);
+  
+  g_free (entries);
+}
+
+void 
+g_desktop_entries_keep_locales (GDesktopEntries *entries,
+			        const gchar * const  *locales)
+{
+  g_return_if_fail (entries != NULL);
+
+  if (entries->locales != NULL)
+    g_strfreev (entries->locales);
+
+  if (locales == NULL)
+    entries->locales = NULL;
+  else
+    {
+      int i, length;
+
+      for (i = 0, length = 1; locales[i] != NULL; i++, length++);
+
+      entries->locales = g_new (char *, length);
+
+      for (i = 0; locales[i] != NULL; i++)
+	entries->locales[i] = g_strdup (locales[i]);
+      entries->locales[i] = NULL;
+    }
+}
+
+static gboolean
+g_deskop_entries_locale_is_interesting (GDesktopEntries  *entries, 
+               				const char       *locale)
+{
+  char *lang, *country, *modifier;
+  const char *interesting_locale;
+  gboolean is_interesting;
+  int i;
+
+  /* NULL means to keep all locales
+   */
+  if (entries->locales == NULL)
+    return TRUE;
+
+  lang = g_desktop_entries_get_locale_lang (locale);
+  country = g_desktop_entries_get_locale_country (locale);
+  modifier = g_desktop_entries_get_locale_modifier (locale);
+
+  is_interesting = FALSE;
+  for (i = 0; (interesting_locale = entries->locales[i]); i++)
+    {
+      char *interesting_locale_lang,
+	   *interesting_locale_country,
+	   *interesting_locale_modifier;
+
+      /* first see if there is an exact match
+       */
+      if (strcasecmp (interesting_locale, locale) == 0)
+	{
+	  is_interesting = TRUE;
+	  break;
+	}
+
+      /* a locale is also interesting if it is a more general version of 
+       * locale already designated interesting
+       */
+
+      if (lang && country && modifier)
+        continue;
+
+      interesting_locale_lang = g_desktop_entries_get_locale_lang (locale);
+      interesting_locale_country = g_desktop_entries_get_locale_country (locale);
+      interesting_locale_modifier = g_desktop_entries_get_locale_modifier (locale);
+
+      if (lang && country 
+	  && interesting_locale_lang
+	  && interesting_locale_country
+	  && interesting_locale_modifier)
+	{
+	  is_interesting = (strcasecmp (lang, interesting_locale_lang) == 0) &&
+			   (strcasecmp (country, interesting_locale_country) == 0);
+
+	  g_free (interesting_locale_lang);
+	  g_free (interesting_locale_country);
+	  g_free (interesting_locale_modifier);
+
+	  if (is_interesting)
+	    break;
+	}
+      else if (lang && modifier
+	       && interesting_locale_lang
+	       && interesting_locale_country
+	       && interesting_locale_modifier)
+	{
+	  is_interesting = (strcasecmp (lang, interesting_locale_lang) == 0) &&
+			   (strcasecmp (modifier, interesting_locale_modifier) == 0);
+	  g_free (interesting_locale_lang);
+	  g_free (interesting_locale_country);
+	  g_free (interesting_locale_modifier);
+
+	  if (is_interesting)
+	    break;
+	}
+      else if (lang)
+	{
+	  is_interesting = (strcasecmp (lang, interesting_locale_lang) == 0);
+
+	  g_free (interesting_locale_lang);
+	  g_free (interesting_locale_country);
+	  g_free (interesting_locale_modifier);
+
+	  if (is_interesting)
+	    break;
+	}
+    }
+
+  g_free (lang);
+  g_free (country);
+  g_free (modifier);
+
+  return is_interesting;
+}
+
+static void
+g_desktop_entries_parse_line (GDesktopEntries  *entries,
+	                      const char       *line,
+			      gsize             length,
+			      GError          **error)
+{
+  GError *parse_error;
+  const gchar *line_start;
+
+  g_return_if_fail (entries != NULL);
+  g_return_if_fail (line != NULL);
+
+  parse_error = NULL;
+
+  line_start = line;
+  while (isspace (*line_start))
+    {
+      line_start++;
+      length--;
+    }
+
+  if (line_is_comment (line_start))
+      g_desktop_entries_parse_comment (entries, line_start, length, &parse_error);
+  else if (line_is_group (line_start))
+      g_desktop_entries_parse_group (entries, line_start, length, &parse_error);
+  else if (line_is_entry (line_start))
+      g_desktop_entries_parse_entry (entries, line_start, length, &parse_error);
+  else
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR, G_DESKTOP_ENTRIES_ERROR_PARSE,
+		   _("desktop entries contain line '%s' which is not "
+		     "an entry, group, or comment"), line);
+      return;
+    }
+
+  if (parse_error)
+    {
+      g_propagate_error (error, parse_error);
+    }
+}
+
+static void
+g_desktop_entries_parse_comment (GDesktopEntries  *entries,
+  	                         const char       *line,
+			         gsize             length,
+			         GError          **error)
+{
+  GDesktopEntry *entry;
+
+  if (!(entries->flags & G_DESKTOP_ENTRIES_KEEP_COMMENTS))
+    return;
+
+  g_assert (entries->current_group != NULL);
+
+  entry = g_new0 (GDesktopEntry, 1);
+
+  entry->key = NULL;
+
+  entry->value = g_new (char, length + 1);
+  strncpy (entry->value, line, length);
+  entry->value[length] = '\0';
+    
+  entries->current_group->entries = g_list_prepend (entries->current_group->entries, entry);
+}
+
+static void
+g_desktop_entries_parse_group (GDesktopEntries  *entries,
+	                       const char       *line,
+			       gsize             length,
+			       GError          **error)
+{
+  gchar *group_name; 
+  const gchar *group_name_start, *group_name_end;
+  glong group_name_length;
+
+  /* advance past opening '[' 
+  */
+  group_name_start = line + 1;
+  group_name_end = line + length - 1;
+
+  while (*group_name_end != ']')
+    group_name_end--;
+
+  group_name_length = group_name_end - group_name_start + 1;
+
+  group_name = g_new (char, group_name_length);
+  strncpy (group_name, group_name_start, group_name_length);
+  group_name[group_name_length - 1] = '\0';
+
+  if (!entries->magic_check_is_done)
+    {
+      if (strcmp (G_DESKTOP_ENTRIES_DEFAULT_GROUP, group_name) != 0)
+	{
+          g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+  		       G_DESKTOP_ENTRIES_ERROR_BAD_DEFAULT_GROUP,
+		       _("desktop entries file does not start with '%s'"),
+		       G_DESKTOP_ENTRIES_DEFAULT_GROUP);
+	  g_free (group_name);
+	  return;
+	}
+      entries->magic_check_is_done = TRUE;
+    }
+
+  g_desktop_entries_add_group (entries, group_name);
+  g_free (group_name);
+}
+
+static void
+g_desktop_entries_parse_entry (GDesktopEntries  *entries,
+	                       const gchar       *line,
+			       gsize             length,
+			       GError          **error)
+{
+  char *key, *value, *key_end, *value_start, *locale;
+  long key_len, value_len;
+
+  key_end = value_start = strstr (line, "=");
+
+  key_end--;
+  value_start++;
+
+  /* Pull the key name from the line (chomping trailing whitespace)
+   */
+  while (isspace (*key_end))
+    key_end--;
+
+  key_len = key_end - line + 2;
+
+  g_assert (key_len < length);
+
+  key = g_new0 (gchar, key_len);
+  strncpy (key, line, key_len);
+  key[key_len - 1] = '\0';
+
+  /* Pull the value from the line (chugging leading whitespace)
+   */
+  while (isspace (*value_start))
+    value_start++;
+
+  value_len = line + length - value_start + 1; 
+
+  value = g_new0 (gchar, value_len);
+  strncpy (value, value_start, value_len);
+  value[value_len - 1] = '\0';
+
+  if (entries->encoding == G_DESKTOP_ENTRIES_ENCODING_UTF8 
+      && !g_utf8_validate (value, -1, NULL))
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		   G_DESKTOP_ENTRIES_ERROR_UNKNOWN_ENCODING,
+		   _("desktop entries contain line '%s' "
+		     "which is not UTF-8"), line);
+
+      g_free (key);
+      g_free (value);
+      return;
+    }
+
+  if (entries->encoding == G_DESKTOP_ENTRIES_ENCODING_GUESS
+        && entries->current_group 
+        && strcmp (entries->current_group->name, 
+		   G_DESKTOP_ENTRIES_DEFAULT_GROUP) == 0
+        && strcmp (key, "Encoding") == 0)
+    {
+      if (strcasecmp (value, "Legacy-Mixed") == 0)
+	  entries->encoding = G_DESKTOP_ENTRIES_ENCODING_MIXED;
+      else if (strcasecmp (value, "UTF-8") == 0)
+	  entries->encoding = G_DESKTOP_ENTRIES_ENCODING_UTF8;
+      else
+	{
+          g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		       G_DESKTOP_ENTRIES_ERROR_UNKNOWN_ENCODING,
+		      _("desktop entries contain unknown encoding '%s'"), value);
+
+	  g_free (key);
+	  g_free (value);
+	  return;
+	}
+    }
+
+  /* Is this key a translation? If so, is it one that we care about?
+   */
+  locale = key_get_locale (key);
+  if (locale == NULL || g_deskop_entries_locale_is_interesting (entries, locale))
+    g_desktop_entries_add_entry (entries, entries->current_group->name, key, value);
+
+  if (locale)
+    g_free (locale);
+
+  g_free (key);
+  g_free (value);
+}
+
+static gchar *
+key_get_locale (const gchar *key)
+{
+  gchar *locale;
+
+  locale = g_strrstr (key, "[");
+
+  if (locale)
+    {
+      locale = g_strdup (locale + 1);
+      locale[strlen (locale)] = '\0';
+    }
+
+  return locale;
+}
+
+void 
+g_desktop_entries_parse_data (GDesktopEntries  *entries,
+	                      const gchar       *data,
+			      gsize             length,
+			      GError          **error)
+{
+  GError *file_error;
+  gsize i;
+
+  g_return_if_fail (entries != NULL);
+
+  file_error = NULL;
+
+  for (i = 0; i < length; i++)
+    {
+      if (data[i] == '\n' && entries->parse_buffer->len > 0)
+        {
+	  g_desktop_entries_flush_parse_buffer (entries, &file_error);
+
+	  if (file_error) 
+	    {
+	      g_propagate_error (error, file_error);
+	      return;
+	    }
+        }
+      else
+	g_string_append_c (entries->parse_buffer, data[i]);
+    }
+
+  entries->approximate_size += length;
+}
+
+void
+g_desktop_entries_flush_parse_buffer (GDesktopEntries  *entries,
+				      GError          **error)
+{
+  GError *file_error = NULL;
+
+  g_return_if_fail (entries != NULL);
+
+  file_error = NULL;
+
+  if (entries->parse_buffer->len > 0) 
+    {
+      g_desktop_entries_parse_line (entries, entries->parse_buffer->str, 
+				    entries->parse_buffer->len,
+				    &file_error);
+      g_string_erase (entries->parse_buffer, 0, -1);
+
+      if (file_error) 
+	{
+	  g_propagate_error (error, file_error);
+	  return;
+	}
+    }
+}
+
+gchar *
+g_desktop_entries_to_data (GDesktopEntries   *entries,
+	                   gsize             *length,
+			   GError           **error)
+{
+  GString *data_string;
+  gchar *data;
+  GList *group_node, *entry_node;
+
+  g_return_val_if_fail (entries != NULL, NULL);
+
+  data_string = g_string_sized_new (2 * entries->approximate_size);
+
+  for (group_node = g_list_last (entries->groups); 
+       group_node != NULL; 
+       group_node = group_node->prev)
+    {
+      GDesktopEntriesGroup *group;
+
+      group = (GDesktopEntriesGroup *) group_node->data;
+
+      if (group->name != NULL)
+	g_string_append_printf (data_string, "[%s]\n", group->name);
+
+      for (entry_node = g_list_last (group->entries);
+	   entry_node != NULL;
+	   entry_node = entry_node->prev)
+	{
+	  GDesktopEntry *entry;
+
+	  entry = (GDesktopEntry *) entry_node->data;
+
+	  if (entry->key != NULL)
+	    g_string_append_printf (data_string, "%s=%s\n", entry->key, entry->value);
+	  else
+	    g_string_append_printf (data_string, "%s\n", entry->value);
+	}
+    }
+
+  if (length)
+    *length = data_string->len;
+
+  data = data_string->str;
+
+  g_string_free (data_string, FALSE);
+
+  return data;
+}
+
+/**
+ * g_desktop_entries_get_keys: 
+ * @entries: a #GDesktopEntries
+ * @group_name: a group name
+ * @length: the number of keys returned
+ * 
+ * Returns all keys for the group name @group. The vector of 
+ * returned keys will be %NULL-terminated, so @length may optionally be %NULL.
+ * 
+ * Return value: a newly-allocated %NULL-terminated array of strings. Use
+ *               g_strfreev() to free it.
+ **/
+gchar **
+g_desktop_entries_get_keys (GDesktopEntries  *entries,
+			    const gchar      *group_name, 
+			    gsize            *length,
+			    GError          **error)
+{
+  GDesktopEntriesGroup *group;
+  GList *tmp;
+  gchar **keys;
+  gsize i, num_keys;
+
+  g_return_val_if_fail (entries != NULL, NULL);
+  g_return_val_if_fail (group_name != NULL, NULL);
+
+  group = g_desktop_entries_lookup_group (entries, group_name);
+
+  if (!group) 
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		   G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND,
+		   _("desktop entries do not have group '%s'"),
+		   group->name);
+      return NULL;
+    }
+
+  num_keys = g_list_length (group->entries);
+
+  keys = (gchar **) g_new (gchar **, num_keys + 1);
+
+  tmp = group->entries;
+  for (i = 0; i < num_keys; i++)
+    {
+      GDesktopEntry *entry;
+
+      entry = (GDesktopEntry *) tmp->data;
+      keys[i] = g_strdup (entry->key);
+
+      tmp = tmp->next;
+    }
+  keys[i] = NULL;
+
+  if (length)
+    *length = num_keys;
+
+  return keys;
+}
+
+/**
+ * g_desktop_entries_get_groups: 
+ * @entries: a #GDesktopEntries
+ * @length: the number of groups returned
+ * 
+ * Returns all groups in the .desktop file loaded with @entries.  The
+ * vector of returned groups will be %NULL-terminated, so @length may 
+ * optionally be %NULL.
+ * 
+ * Return value: a newly-allocated %NULL-terminated array of strings. Use
+ *               g_strfreev() to free it.
+ **/
+gchar **
+g_desktop_entries_get_groups (GDesktopEntries *entries, 
+			      gsize           *length)
+{
+  GList *tmp;
+  gchar **groups;
+  gsize i, num_groups;
+
+  g_return_val_if_fail (entries != NULL, NULL);
+
+  num_groups = g_list_length (entries->groups);
+  groups = (gchar **) g_new (gchar **, num_groups + 1);
+
+  tmp = entries->groups;
+  for (i = 0; i < num_groups; i++)
+    {
+      GDesktopEntriesGroup *group;
+
+      group = (GDesktopEntriesGroup *) tmp->data;
+      groups[i] = g_strdup (group->name);
+
+      tmp = tmp->next;
+    }
+  groups[i] = NULL;
+
+  if (length)
+    *length = num_groups;
+
+  return groups;
+}
+
+char *
+g_desktop_entries_get_value (GDesktopEntries  *entries,
+			     const gchar      *group_name,
+			     const gchar      *key, 
+			     GError          **error)
+{
+  GDesktopEntriesGroup *group;
+  GDesktopEntry *entry;
+  gchar *value;
+
+  g_return_val_if_fail (entries != NULL, NULL);
+  g_return_val_if_fail (group_name != NULL, NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  entry = NULL;
+  value = NULL;
+
+  group = g_desktop_entries_lookup_group (entries, group_name);
+
+  if (!group)
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		   G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND,
+		   _("desktop entries do not have group '%s'"),
+		   group->name);
+      return NULL;
+    }
+
+  entry = g_desktop_entries_lookup_entry (entries, group, key);
+
+  if (entry)
+    value = g_strdup (entry->value);
+  else
+    g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+  	         G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND,
+	         _("desktop entries do not have key '%s'"), key);
+
+  return value;
+}
+
+/**
+ * g_desktop_entries_get_string: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @error: return location for a #GError
+ *
+ * Returns the value associated with @key under @group.  In the event
+ * the key cannot be found, %NULL is returned and @error is set to
+ * #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND.  In the event that the @group
+ * cannot be found, %NULL is returned and @error is set to 
+ * #G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND.
+ * 
+ * Return value: a string or %NULL if the specified key cannot be
+ *               found.
+ **/
+char *
+g_desktop_entries_get_string (GDesktopEntries  *entries,
+			      const gchar      *group,
+			      const gchar      *key, 
+			      GError          **error)
+{
+  char *value, *string_value;
+  GError *entries_error;
+
+  g_return_val_if_fail (entries != NULL, NULL);
+  g_return_val_if_fail (group != NULL, NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  entries_error = NULL;
+
+  value = g_desktop_entries_get_value (entries, group, key, &entries_error);
+
+  if (entries_error)
+    {
+      g_propagate_error (error, entries_error);
+      return NULL;
+    }
+
+  string_value = g_desktop_entries_parse_value_as_string (entries, value, &entries_error);
+  g_free (value);
+
+  if (entries_error)
+    {
+      if (g_error_matches (entries_error,
+			   G_DESKTOP_ENTRIES_ERROR,
+			   G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE))
+	{
+	  g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		       G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		       _("desktop entries contain key '%s' "
+			 "which has value that cannot be interpreted."),
+		       key);
+	  g_error_free (entries_error);
+	}
+      else
+        g_propagate_error (error, entries_error);
+    }
+
+  return string_value;
+}
+
+/**
+ * g_desktop_entries_get_string_list: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @length: the number of localized strings returned
+ * @error: return location for a #GError
+ *
+ * Returns the values associated with @key under @group.  In the event
+ * the key cannot be found, %NULL is returned and @error is set to
+ * #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND.  In the event that the @group
+ * cannot be found, %NULL is returned and @error is set to 
+ * #G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND.
+ * 
+ * Return value: a string or %NULL if the specified key cannot be
+ *               found.
+ **/
+gchar **
+g_desktop_entries_get_string_list (GDesktopEntries  *entries,
+				   const gchar    *group,
+				   const gchar    *key,
+				   gsize          *length,
+				   GError        **error)
+{
+  GError *entries_error;
+  gchar **value_vector, *value;
+  gint last_char_index;
+
+  entries_error = NULL;
+
+  value = g_desktop_entries_get_string (entries, group, key, &entries_error);
+
+  if (entries_error)
+    g_propagate_error (error, entries_error);
+
+  if (!value)
+    return NULL;
+
+  last_char_index = strlen (value) - 1;
+
+  if (value[last_char_index] == ';')
+    value[last_char_index] = '\0';
+
+  value_vector = g_strsplit (value, ";", 0);
+  g_free (value);
+
+  if (length)
+    for (*length = 0; value_vector[*length]; (*length)++);
+
+  return value_vector;
+}
+
+/**
+ * g_desktop_entries_get_locale_string: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @locale: a locale or %NULL
+ * @error: return location for a #GError
+ * 
+ * Returns the value associated with @key under @group translated in the
+ * given @locale if available.  If @locale is %NULL then the current
+ * locale is assumed. If @key cannot be found then %NULL is returned
+ * and @error is set to #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND. If the 
+ * value associated with @key cannot be interpreted or no suitable 
+ * translation can be found then the untranslated value is returned and 
+ * @error is set to #G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE and 
+ * #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND, respectively. In the event that 
+ * the @group cannot be found, %NULL is returned and @error is set to 
+ * #G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND.
+ * 
+ * Return value: a string or %NULL if the specified key cannot be
+ *               found.
+ **/
+gchar *
+g_desktop_entries_get_locale_string (GDesktopEntries  *entries,
+				     const gchar    *group,
+				     const gchar    *key,
+				     const gchar    *locale,
+				     GError        **error)
+{
+  gchar *lang, *country, *modifier, *candidate_key, *utf8_value,
+    *translated_value;
+  GError *entries_error;
+
+  entries_error = NULL;
+
+  if (!locale)
+    {
+#ifdef HAVE_LC_MESSAGES
+      locale = (const gchar *) setlocale (LC_MESSAGES, NULL);
+#else
+      locale = (const gchar *) setlocale (LC_CTYPE, NULL);
+#endif
+
+      if (!locale)
+	locale = (const gchar *) "C";
+    }
+
+  if (!g_desktop_entries_has_group (entries, group))
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		   G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND,
+		   _("desktop entries do not have group '%s'"),
+		   group);
+      return NULL;
+    }
+
+  lang = g_desktop_entries_get_locale_lang (locale);
+  country = g_desktop_entries_get_locale_country (locale);
+  modifier = g_desktop_entries_get_locale_modifier (locale);
+
+  if (lang && country && modifier)
+    {
+      candidate_key = g_strdup_printf ("%s[%s_%s %s]",
+				       key, lang, country, modifier);
+
+      translated_value = g_desktop_entries_get_string (entries,
+						       group,
+						       candidate_key, NULL);
+      g_free (candidate_key);
+    }
+  else if (lang && country)
+    {
+      candidate_key = g_strdup_printf ("%s[%s_%s]", key, lang, country);
+
+      translated_value = g_desktop_entries_get_string (entries, group,
+						       candidate_key, NULL);
+      g_free (candidate_key);
+    }
+  else if (lang && modifier)
+    {
+      candidate_key = g_strdup_printf ("%s[%s %s]", key, lang, modifier);
+
+      translated_value = g_desktop_entries_get_string (entries, group,
+						       candidate_key, NULL);
+      g_free (candidate_key);
+    }
+  else if (lang)
+    {
+      candidate_key = g_strdup_printf ("%s[%s]", key, lang);
+
+      translated_value = g_desktop_entries_get_string (entries, group,
+						       candidate_key, NULL);
+      g_free (candidate_key);
+    }
+
+  if (translated_value)
+    {
+      utf8_value = g_desktop_entries_key_to_utf8 (entries, candidate_key,
+						  translated_value);
+
+      if (!utf8_value)
+	{
+	  g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		       G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		       _("desktop entries contain key '%s' "
+			 "which has value that cannot be interpreted."),
+		       candidate_key);
+
+	  translated_value = NULL;
+	}
+
+      if (translated_value)
+	g_free (translated_value);
+
+      translated_value = utf8_value;
+    }
+
+  /* Fallback to untranslated key
+   */
+  if (!translated_value)
+    {
+      translated_value = g_desktop_entries_get_string (entries, group, key,
+   						       &entries_error);
+
+      if (!translated_value)
+	g_propagate_error (error, entries_error);
+      else
+	g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		     G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND,
+		     _("desktop entries contain no translated value "
+		       "for key '%s' with locale '%s'."),
+		     key, locale);
+    }
+
+  return translated_value;
+}
+
+/**
+ * g_desktop_entries_get_locale_string_list: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @locale: a locale 
+ * @length: the number of localized strings returned
+ * @error: return location for a #GError
+ * 
+ * Returns the values associated with @key under @group translated in the
+ * given @locale if available.  If @locale is %NULL then the current
+ * locale is assumed. If @key cannot be found then %NULL is returned
+ * and @error is set to #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND. If the 
+ * values associated with @key cannot be interpreted or no suitable 
+ * translations can be found then the untranslated values are returned and 
+ * @error is set to #G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE and 
+ * #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND, respectively. In the event that 
+ * the @group cannot be found, %NULL is returned and @error is set to 
+ * #G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND.  The vector of returned strings
+ * will be %NULL-terminated, so @length may optionally be %NULL.
+ * 
+ * Return value: a string or %NULL if the specified key cannot be
+ *               found.
+ **/
+gchar **
+g_desktop_entries_get_locale_string_list (GDesktopEntries  *entries,
+					  const gchar    *group,
+					  const gchar    *key,
+					  const gchar    *locale,
+					  gsize          *length,
+					  GError        **error)
+{
+  GError *entries_error;
+  gchar **value_vector, *value;
+
+  entries_error = NULL;
+
+  value = g_desktop_entries_get_locale_string (entries, group, key, locale,
+					       &entries_error);
+
+  if (entries_error)
+    g_propagate_error (error, entries_error);
+
+  if (!value)
+    return NULL;
+
+  if (value[strlen (value) - 1] == ';')
+    {
+      value[strlen (value) - 1] = '\0';
+    }
+
+  value_vector = g_strsplit (value, ";", 0);
+
+  g_free (value);
+
+  if (length)
+    for (*length = 0; value_vector[*length]; *(length)++);
+
+  return value_vector;
+}
+
+/**
+ * g_desktop_entries_get_boolean: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @error: return location for a #GError
+ * 
+ * Returns the value associated with @key under @group as a boolean. 
+ * If @key cannot be found then the return value is undefined
+ * and @error is set to #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND. Likewise,
+ * if the value associated with @key cannot be interpreted as a 
+ * boolean then the return value is also undefined and @error is set
+ * to #G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE.
+ * 
+ * Return value: the value associated with the key as a boolean
+ **/
+gboolean
+g_desktop_entries_get_boolean (GDesktopEntries  *entries,
+			       const gchar      *group,
+			       const gchar      *key,
+			       GError          **error)
+{
+  GError *entries_error;
+  gchar *value;
+  gboolean bool_value;
+
+  entries_error = NULL;
+
+  value = g_desktop_entries_get_value (entries, group, key, &entries_error);
+
+  if (!value)
+    {
+      g_propagate_error (error, entries_error);
+      return FALSE;
+    }
+
+  bool_value = g_desktop_entries_parse_value_as_boolean (entries, value,
+						       &entries_error);
+  g_free (value);
+
+  if (entries_error)
+    {
+      if (g_error_matches (entries_error,
+			   G_DESKTOP_ENTRIES_ERROR,
+			   G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE))
+	{
+	  g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		       G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		       _("desktop entries contain key '%s' "
+			 "which has value that cannot be interpreted."),
+		       key);
+	  g_error_free (entries_error);
+	}
+      else
+        g_propagate_error (error, entries_error);
+    }
+
+  return bool_value;
+}
+
+/**
+ * g_desktop_entries_get_boolean_list: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @length: the number of booleans returned
+ * @error: return location for a #GError
+ * 
+ * Returns the values associated with @key under @group as booleans. 
+ * If @key cannot be found then the return value is undefined
+ * and @error is set to #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND. Likewise,
+ * if the values associated with @key cannot be interpreted as 
+ * booleans then the return value is also undefined and @error is set
+ * to #G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE.
+ * 
+ * Return value: the values associated with the key as a boolean
+ **/
+gboolean *
+g_desktop_entries_get_boolean_list (GDesktopEntries  *entries,
+				    const gchar    *group,
+				    const gchar    *key,
+				    gsize          *length,
+				    GError        **error)
+{
+  GError *entries_error;
+  gchar **value_vector;
+  gboolean *bool_values;
+  gsize i, num_bools;
+
+  entries_error = NULL;
+
+  value_vector = g_desktop_entries_get_string_list (entries, group, key,
+				 		    &num_bools, &entries_error);
+
+  if (entries_error)
+    g_propagate_error (error, entries_error);
+
+  if (!value_vector)
+    return NULL;
+
+  bool_values = g_new (gboolean, num_bools);
+
+  for (i = 0; i < num_bools; i++)
+    {
+      bool_values[i] = g_desktop_entries_parse_value_as_boolean (entries,
+							         value_vector[i],
+							         &entries_error);
+
+      if (entries_error)
+	{
+	  g_propagate_error (error, entries_error);
+	  g_strfreev (value_vector);
+	  g_free (bool_values);
+
+	  return NULL;
+	}
+    }
+  g_strfreev (value_vector);
+
+  if (length)
+    *length = num_bools;
+
+  return bool_values;
+}
+
+/**
+ * g_desktop_entries_get_integer: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @error: return location for a #GError
+ * 
+ * Returns the value associated with @key under @group as an integer. 
+ * If @key cannot be found then the return value is undefined
+ * and @error is set to #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND. Likewise,
+ * if the value associated with @key cannot be interpreted as an 
+ * integer then the return value is also undefined and @error is set
+ * to #G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE.
+ * 
+ * Return value: the value associated with the key as an integer.
+ **/
+gint
+g_desktop_entries_get_integer (GDesktopEntries  *entries,
+			       const gchar    *group,
+			       const gchar    *key, 
+			       GError        **error)
+{
+  GError *entries_error;
+  gchar *value;
+  gint int_value;
+
+  entries_error = NULL;
+
+  value = g_desktop_entries_get_value (entries, group, key, &entries_error);
+
+  if (entries_error)
+    {
+      g_propagate_error (error, entries_error);
+      return 0;
+    }
+
+  int_value = g_desktop_entries_parse_value_as_integer (entries, value,
+	  					        &entries_error);
+  g_free (value);
+
+  if (entries_error)
+    {
+      if (g_error_matches (entries_error,
+			   G_DESKTOP_ENTRIES_ERROR,
+			   G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE))
+	{
+	  g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		       G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		       _("desktop entries contain key '%s' "
+			 "which has value that cannot be interpreted."), key);
+	  g_error_free (entries_error);
+	}
+      else
+        g_propagate_error (error, entries_error);
+    }
+
+  return int_value;
+}
+
+/**
+ * g_desktop_entries_get_integer_list: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * @key: a key
+ * @length: the number of integers returned
+ * @error: return location for a #GError
+ * 
+ * Returns the values associated with @key under @group as integers. 
+ * If @key cannot be found then the return value is undefined
+ * and @error is set to #G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND. Likewise,
+ * if the values associated with @key cannot be interpreted as 
+ * integers then the return value is also undefined and @error is set
+ * to #G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE.
+ * 
+ * Return value: the values associated with the key as a integer
+ **/
+gint *
+g_desktop_entries_get_integer_list (GDesktopEntries  *entries,
+				    const gchar    *group,
+				    const gchar    *key,
+				    gsize          *length,
+				    GError        **error)
+{
+  GError *entries_error;
+  gchar **value_vector;
+  gint *int_values;
+  gsize i, num_ints;
+
+  entries_error = NULL;
+
+  value_vector = g_desktop_entries_get_string_list (entries, group, key,
+						  &num_ints, &entries_error);
+
+  if (entries_error)
+    g_propagate_error (error, entries_error);
+
+  if (!value_vector)
+    return NULL;
+
+  int_values = g_new (int, num_ints);
+
+  for (i = 0; i < num_ints; i++)
+    {
+      int_values[i] = g_desktop_entries_parse_value_as_integer (entries,
+							      value_vector[i],
+							      &entries_error);
+
+      if (entries_error)
+	{
+	  g_propagate_error (error, entries_error);
+	  g_strfreev (value_vector);
+	  g_free (int_values);
+
+	  return NULL;
+	}
+    }
+  g_strfreev (value_vector);
+
+  if (length)
+    *length = num_ints;
+
+  return int_values;
+}
+
+/**
+ * g_desktop_entries_has_group: 
+ * @entries: a #GDesktopEntries
+ * @group: a group name
+ * 
+ * Looks whether the .desktop file has the group @group.
+ * 
+ * Return value: %TRUE if @group is a part of @entries, %FALSE otherwise.
+ **/
+gboolean
+g_desktop_entries_has_group (GDesktopEntries  *entries,
+			     const char       *group_name)
+{
+  GList *tmp;
+
+  g_return_val_if_fail (entries != NULL, FALSE);
+  g_return_val_if_fail (group_name != NULL, FALSE);
+
+  for (tmp = entries->groups; tmp != NULL; tmp = tmp->next)
+    {
+      GDesktopEntriesGroup *group;
+
+      group = (GDesktopEntriesGroup *) tmp->data;
+
+      if (group && group->name && strcmp (group->name, group_name) == 0)
+	return TRUE;
+    }
+
+  return FALSE;
+}
+
+void
+g_desktop_entries_add_group (GDesktopEntries *entries,
+			     const gchar     *group_name)
+{
+  GDesktopEntriesGroup *group;
+
+  g_return_if_fail (entries != NULL);
+  g_return_if_fail (group_name != NULL);
+  g_return_if_fail (g_desktop_entries_lookup_group (entries, group_name) == NULL);
+
+  group = g_new0 (GDesktopEntriesGroup, 1);
+  group->name = g_strdup (group_name);
+  
+  if (entries->flags & G_DESKTOP_ENTRIES_GENERATE_LOOKUP_MAP)
+    g_datalist_init (&group->lookup_map);
+
+  entries->groups = g_list_prepend (entries->groups, group);
+
+  entries->current_group = group;
+}
+
+void
+g_desktop_entries_remove_group (GDesktopEntries *entries,
+				const gchar     *group_name)
+{
+  GDesktopEntriesGroup *group;
+
+  g_return_if_fail (entries != NULL);
+  g_return_if_fail (group_name != NULL);
+
+  group = g_desktop_entries_lookup_group (entries, group_name);
+
+  if (!group)
+    return;
+
+  /* FIXME: maybe would be better to have this point to a 
+   * valid nearby group
+   */
+  if (entries->current_group == group)
+    entries->current_group = NULL;
+
+  entries->groups = g_list_remove (entries->groups, group);
+
+  g_free ((gchar *) group->name);
+
+  g_list_foreach (group->entries, (GFunc) g_free, NULL);
+  g_list_free (group->entries);
+
+  if (group->lookup_map)
+    g_datalist_clear (&group->lookup_map);
+
+  g_free (group);
+}
+
+void
+g_desktop_entries_add_entry (GDesktopEntries *entries,
+			     const gchar     *group_name,
+			     const gchar     *key,
+			     const gchar     *value)
+{
+  GList *list;
+  GDesktopEntriesGroup *group;
+  GDesktopEntry *entry;
+
+  if (group_name == NULL)
+    group = entries->current_group;
+
+  group = g_desktop_entries_lookup_group (entries, group_name);
+
+  if (!group) 
+    {
+      g_desktop_entries_add_group (entries, group_name); 
+      group = (GDesktopEntriesGroup *) entries->groups->data;
+    }
+
+  entry = g_new0 (GDesktopEntry, 1);
+
+  entry->key = g_strdup (key);
+  entry->value =  g_strdup (value);
+    
+  if (entries->flags & G_DESKTOP_ENTRIES_GENERATE_LOOKUP_MAP)
+    g_datalist_set_data (&group->lookup_map, key, list); 
+
+  group->entries = g_list_prepend (group->entries, entry);
+}
+
+void
+g_desktop_entries_remove_entry (GDesktopEntries *entries,
+				const gchar   *group_name,
+				const gchar   *key)
+{
+  GList *list;
+  GDesktopEntriesGroup *group;
+  GDesktopEntry *entry;
+
+  if (group_name == NULL)
+    group = entries->current_group;
+
+  group = g_desktop_entries_lookup_group (entries, group_name);
+
+  if (group == NULL)
+    return;
+
+  group->entries = g_list_remove (group->entries, entry);
+
+  entry = g_desktop_entries_lookup_entry (entries, group, key); 
+
+  if (entry == NULL)
+    return;
+
+  g_datalist_remove_no_notify (group->lookup_map, entry->key);
+
+  g_free (entry->key);
+  g_free (entry->value);
+  g_free (entry);
+}
+
+static GDesktopEntriesGroup *
+g_desktop_entries_lookup_group (GDesktopEntries *entries, 
+				const gchar     *group_name)
+{
+  GDesktopEntriesGroup *group;
+  GList *tmp;
+
+  group = NULL;
+  for (tmp = entries->groups; tmp != NULL; tmp = tmp->next)
+    {
+      group = (GDesktopEntriesGroup *) tmp->data;
+
+      if (group && group->name && strcmp (group->name, group_name) == 0)
+        break;
+
+      group = NULL;
+    }
+
+  return group;
+}
+
+static GDesktopEntry *
+g_desktop_entries_lookup_entry (GDesktopEntries       *entries,
+				GDesktopEntriesGroup  *group,
+				const gchar           *key)
+{
+  GList *tmp;
+  GDesktopEntry *entry;
+
+  if (entries->flags & G_DESKTOP_ENTRIES_GENERATE_LOOKUP_MAP)
+    return (GDesktopEntry *) g_datalist_get_data (&group->lookup_map, key);
+
+  entry = NULL;
+  for (tmp = group->entries; tmp != NULL; tmp = tmp->next)
+    {
+      entry = (GDesktopEntry *) tmp->data;
+
+      if (entry->key && (strcmp (key, entry->key) == 0))
+	break;
+
+      entry = NULL;
+    }
+
+  return entry;
+}
+
+static gchar *
+g_desktop_entries_get_locale_modifier (const gchar *locale)
+{
+  gchar *p;
+
+  p = g_strrstr (locale, "@");
+
+  if (!p)
+    return NULL;
+
+  return g_strdup (p + 1);
+}
+
+static gchar *
+g_desktop_entries_get_locale_country (const gchar *locale)
+{
+  int country_len;
+  gchar *country, *p, *q;
+
+  p = strstr (locale, "_");
+
+  if (!p)
+    return NULL;
+
+  p++;
+
+  q = strstr (p, ".");
+
+  if (!q)
+    q = strstr (p, "@");
+
+  if (!q)
+    country_len = q - p;
+  else
+    country_len = strlen (p);
+
+  if (country_len <= 0)
+    return NULL;
+
+  country = g_new (gchar, country_len + 1);
+
+  strncpy (country, p, country_len);
+  country[country_len] = '\0';
+
+  return country;
+}
+
+static gchar *
+g_desktop_entries_get_locale_lang (const gchar *locale)
+{
+  int lang_len;
+  gchar *lang, *p;
+
+  p = strstr (locale, "_");
+
+  if (!p)
+    p = strstr (locale, ".");
+
+  if (!p)
+    p = strstr (locale, "@");
+
+  if (p)
+    {
+      lang_len = p - locale;
+      lang = g_new (gchar, lang_len + 1);
+      strncpy (lang, locale, lang_len);
+      lang[lang_len] = '\0';
+    }
+  else
+    {
+      lang = g_strdup (locale);
+    }
+
+  return lang;
+}
+
+static gchar *
+g_desktop_entries_get_locale_encoding (const gchar *locale)
+{
+  int encoding_len;
+  gchar *encoding, *p, *q;
+
+  p = strstr (locale, ".");
+
+  if (!p)
+    return NULL;
+
+  p++;
+
+  q = strstr (p, "@");
+
+  if (!q)
+    encoding_len = q - p;
+  else
+    encoding_len = strlen (p);
+
+  if (encoding_len <= 0)
+    {
+      return g_desktop_entries_get_fallback_encoding (locale);
+    }
+  else
+    {
+      encoding = g_new (gchar, encoding_len + 1);
+
+      strncpy (encoding, p, encoding_len);
+      encoding[encoding_len] = '\0';
+    }
+
+  return encoding;
+}
+
+static gchar *
+g_desktop_entries_get_fallback_encoding (const gchar *locale)
+{
+  gchar *locale_lang, *locale_country, **tag_vector,
+    *tag, *tag_lang, *tag_country;
+  const gchar *tags;
+
+  static const gchar *tag_encoding_table[] = {
+    "hy", "ARMSCII-8",
+    "zh_TW", "BIG5",
+    "be bg", "CP1251",
+    "zh_CN", "EUC-CN",
+    "ja", "EUC-JP",
+    "ko", "EUC-KR",
+    "ka", "GEORGIAN-PS",
+    "br ca da de en es eu fi fr gl it nl no pt sv wa", "ISO-8859-1",
+    "cs hr hu pl ro sk sl sq sr", "ISO-8859-2",
+    "eo", "ISO-8859-3",
+    "mk sp", "ISO-8859-5",
+    "el", "ISO-8859-7",
+    "tr", "ISO-8859-9",
+    "lt lv mi", "ISO-8859-13",
+    "cy ga", "ISO-8859-14",
+    "et", "ISO-8859-15",
+    "ru", "KOI8-R",
+    "uk", "KOI8-U",
+    "vi", "TCVN-5712",
+    "th", "TIS-620",
+    NULL
+  };
+
+  int i, j;
+
+  locale_lang = g_desktop_entries_get_locale_lang (locale);
+  locale_country = g_desktop_entries_get_locale_country (locale);
+
+  i = 0;
+  for (tags = tag_encoding_table[i]; (tags = tag_encoding_table[i]); i++)
+    {
+      tag_vector = g_strsplit (tags, " ", 0);
+
+      j = 0;
+      for (tag = tag_vector[j]; (tag = tag_vector[j]); j++)
+	{
+	  tag_lang = g_desktop_entries_get_locale_lang (tag);
+	  tag_country = g_desktop_entries_get_locale_country (tag);
+
+	  if (strcmp (locale_lang, tag_lang) == 0
+	      && ((!locale_country && !tag_country)
+		  || (locale_country && tag_country
+		      && strcmp (locale_country, tag_country) == 0)))
+	    {
+	      g_free (tag_lang);
+	      g_free (tag_country);
+	      g_strfreev (tag_vector);
+
+	      return g_strdup (tag_encoding_table[i + 1]);
+	    }
+
+	  g_free (tag_lang);
+	  g_free (tag_country);
+	}
+
+      g_strfreev (tag_vector);
+    }
+
+  return NULL;
+}
+
+/* Lines starting with # or consisting entirely of whitespace are merely
+ * recorded, not parsed. (This function assumes all leading whitespaces 
+ * have been stripped)
+ */
+static gboolean
+line_is_comment (const gchar *line)
+{
+  return (*line == '#' || *line == '\0');
+}
+
+/* A group in a desktop entries is made up of a starting '[' followed by one
+ * or more letters making up the group name followed by ']'.
+ */
+static gboolean
+line_is_group (const gchar *line)
+{
+  gchar *p;
+
+  p = (gchar *) line;
+  if (*p != '[')
+    return FALSE;
+
+  p = g_utf8_next_char (p);
+
+  if (!*p)
+    return FALSE;
+
+  p = g_utf8_next_char (p);
+
+  /* Group name must be non-empty
+   */
+  if (*p == ']')
+    return FALSE;
+
+  while (*p && *p != ']')
+    p = g_utf8_next_char (p);
+
+  if (!*p)
+    return FALSE;
+
+  return TRUE;
+}
+
+/* An entry in a desktop entries is made up of a key/value pair separated by
+ * an equal sign (=)
+ */
+static gboolean
+line_is_entry (const gchar *line)
+{
+  gchar *p;
+
+  p = (gchar *) g_utf8_strchr (line, -1, '=');
+
+  if (!p)
+    return FALSE;
+
+  /* Key must be non-empty
+   */
+  if (*p == line[0])
+    return FALSE;
+
+  /* Value must be non-empty
+   */
+  p++;
+  if (!*p)
+    return FALSE;
+
+  return TRUE;
+}
+
+static gchar *
+g_desktop_entries_key_to_utf8 (GDesktopEntries *entries,
+			       const gchar     *key,
+			       const gchar     *value)
+{
+  if (entries->encoding == G_DESKTOP_ENTRIES_ENCODING_UTF8
+      || entries->encoding == G_DESKTOP_ENTRIES_ENCODING_GUESS)
+    {
+      if (g_utf8_validate (value, -1, NULL))
+	return g_strdup (value);
+
+      if (entries->encoding == G_DESKTOP_ENTRIES_ENCODING_UTF8)
+	return NULL;
+    }
+
+  if (entries->encoding == G_DESKTOP_ENTRIES_ENCODING_MIXED
+      || entries->encoding == G_DESKTOP_ENTRIES_ENCODING_GUESS)
+    {
+      char *encoding, *locale;
+
+      locale = key_get_locale (key);
+
+      if (locale == NULL)
+	locale = g_strdup ("C");
+
+      encoding = g_desktop_entries_get_locale_encoding (locale);
+      g_free (locale);
+
+      return g_convert (value, strlen (value), "utf8", encoding, NULL, NULL,
+		    NULL);
+
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static gchar *
+g_desktop_entries_parse_value_as_string (GDesktopEntries  *entries,
+					 const gchar      *value,
+					 GError          **error)
+{
+  GError *parse_error;
+  gchar *string_value, *p, *q;
+  gsize length;
+
+  parse_error = NULL;
+
+  length = strlen (value) + 1;
+
+  string_value = g_new (gchar, length);
+
+  p = (gchar *) value;
+  q = string_value;
+  while (p < (value + length - 1))
+    {
+      if (*p == '\\')
+	{
+	  p++;
+	  
+	  switch (*p)
+	    {
+	      case 's':
+                  *q = ' ';
+		  length--;
+              break;
+
+              case 'n':
+                  *q = '\n';
+		  length--;
+              break;
+
+              case 't':
+                  *q = '\t';
+		  length--;
+              break;
+
+              case 'r':
+                  *q = '\r';
+		  length--;
+              break;
+
+              case '\\':
+                  *q = '\\';
+		  length--;
+              break;
+
+              default:
+	         *q++ = '\\';
+		 *q = *p;
+
+		 if (parse_error == NULL)
+		   {
+		     char sequence[3];
+
+		     sequence[0] = '\\';
+		     sequence[1] = *p;
+		     sequence[2] = '\0';
+
+		     g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		                  G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		                  _("desktop entries contain invalid escape "
+				    "sequence '%s'"), sequence);
+		   }
+	      break;
+	    }
+	}
+      else
+	*q = *p;
+
+      q++;
+      p++;
+    }
+
+  if (p[-1] == '\\' && error == NULL)
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+                   G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		   _("desktop entries contain escape character at end of "
+		     "line"));
+    }
+
+  *q = '\0';
+
+  return string_value;
+}
+
+static gint
+g_desktop_entries_parse_value_as_integer (GDesktopEntries  *entries,
+					  const gchar      *value,
+					  GError          **error)
+{
+  gchar *end_of_valid_int;
+  gint int_value;
+
+  int_value = 0;
+
+  int_value = strtol (value, &end_of_valid_int, 0);
+
+  if (*end_of_valid_int != '\0')
+    {
+      g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+		   G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+		   _("Value '%s' cannot be interpreted as a number."), value);
+    }
+
+  return int_value;
+}
+
+static gboolean
+g_desktop_entries_parse_value_as_boolean (GDesktopEntries  *entries,
+					  const gchar      *value,
+					  GError          **error)
+{
+  if (value)
+    {
+      if ((strcmp (value, "1") == 0) || (strcasecmp (value, "true") == 0)
+	  || (strcasecmp (value, "yes") == 0)
+	  || (strcasecmp (value, "t") == 0) || (strcasecmp (value, "y") == 0))
+	return TRUE;
+      else if ((strcmp (value, "0") == 0)
+	       || (strcasecmp (value, "false") == 0)
+	       || (strcasecmp (value, "no") == 0)
+	       || (strcasecmp (value, "f") == 0)
+	       || (strcasecmp (value, "n") == 0))
+	return FALSE;
+    }
+
+  g_set_error (error, G_DESKTOP_ENTRIES_ERROR,
+	       G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE,
+	       _("Value '%s' cannot be interpreted as a boolean."), value);
+  return FALSE;
+}
Index: gdesktopentries.h
===================================================================
RCS file: gdesktopentries.h
diff -N gdesktopentries.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gdesktopentries.h	29 Apr 2004 22:13:05 -0000
@@ -0,0 +1,146 @@
+/* gdesktopentries.h - desktop entry file parser
+ *
+ *  Copyright 2004  Ray Strode <halfline hawaii rr com>
+ *
+ * GLib 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.
+ *
+ * GLib 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 GLib; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __G_DESKTOP_ENTRIES_H__
+#define __G_DESKTOP_ENTRIES_H__
+
+#include <glib/gerror.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  G_DESKTOP_ENTRIES_ERROR_UNKNOWN_ENCODING,
+  G_DESKTOP_ENTRIES_ERROR_BAD_DEFAULT_GROUP,
+  G_DESKTOP_ENTRIES_ERROR_PARSE,
+  G_DESKTOP_ENTRIES_ERROR_NO_FILE,
+  G_DESKTOP_ENTRIES_ERROR_KEY_NOT_FOUND,
+  G_DESKTOP_ENTRIES_ERROR_GROUP_NOT_FOUND,
+  G_DESKTOP_ENTRIES_ERROR_INVALID_VALUE
+} GDesktopEntriesError;
+
+#define G_DESKTOP_ENTRIES_ERROR g_desktop_entries_error_quark()
+
+GQuark g_desktop_entries_error_quark (void);
+
+#define G_DESKTOP_ENTRIES_DEFAULT_GROUP "Desktop Entry"
+
+typedef struct _GDesktopEntries GDesktopEntries;
+
+typedef enum
+{
+  G_DESKTOP_ENTRIES_NONE                 = 0,
+  G_DESKTOP_ENTRIES_KEEP_COMMENTS        = 1 << 0,
+  G_DESKTOP_ENTRIES_KEEP_TRANSLATIONS    = 1 << 1,
+  G_DESKTOP_ENTRIES_GENERATE_LOOKUP_MAP  = 1 << 2
+} GDesktopEntriesFlags;
+
+GDesktopEntries         *g_desktop_entries_new           (GDesktopEntriesFlags flags);
+GDesktopEntries         *g_desktop_entries_new_from_file (GDesktopEntriesFlags   flags,
+ 	                                                  const gchar         *file,
+                                                          GError             **error);
+void                   g_desktop_entries_free            (GDesktopEntries  *entries);
+
+void                   g_desktop_entries_keep_locales    (GDesktopEntries *entries,
+						          const gchar * const *locales);
+
+void                   g_desktop_entries_parse_data (GDesktopEntries  *entries,
+	                                             const gchar    *data,
+						     gsize          length,
+						     GError        **error);  
+void                   g_desktop_entries_flush_parse_buffer (GDesktopEntries  *entries,
+		 		                             GError          **error);
+
+gchar                 *g_desktop_entries_to_data   (GDesktopEntries   *entries,
+	                                            gsize             *length,
+						    GError           **error);
+
+gchar                **g_desktop_entries_get_groups (GDesktopEntries  *entries,
+	                                           gsize          *length);
+gchar                **g_desktop_entries_get_keys   (GDesktopEntries  *entries,
+				                   const gchar    *group_name,
+						   gsize            *length,
+						   GError        **error);
+gboolean               g_desktop_entries_has_group  (GDesktopEntries  *entries,
+	                                           const char     *group);
+
+gchar                 *g_desktop_entries_get_value         (GDesktopEntries  *entries,
+				                          const gchar    *group,
+				                          const gchar    *key,
+						          GError        **error);
+gchar                 *g_desktop_entries_get_string        (GDesktopEntries  *entries,
+				                          const gchar    *group,
+				                          const gchar    *key,
+						          GError        **error);
+gchar                 *g_desktop_entries_get_locale_string (GDesktopEntries  *entries,
+					                  const gchar    *group,
+					                  const gchar    *key,
+					                  const gchar    *locale,
+					                  GError        **error);
+gboolean               g_desktop_entries_get_boolean       (GDesktopEntries  *entries,
+				                          const gchar    *group,
+				                          const gchar    *key,
+							  GError        **error);
+gint                   g_desktop_entries_get_integer       (GDesktopEntries  *entries,
+				                          const gchar    *group,
+				                          const gchar    *key,
+							  GError        **error);
+
+gchar                **g_desktop_entries_get_string_list        (GDesktopEntries  *entries,
+					                       const gchar    *group,
+					                       const gchar    *key,
+					                       gsize          *length,
+					                       GError        **error);
+gchar                **g_desktop_entries_get_locale_string_list (GDesktopEntries  *entries,
+						               const gchar    *group,
+						               const gchar    *key,
+						               const gchar    *locale,
+						               gsize          *length,
+						               GError        **error);
+gboolean              *g_desktop_entries_get_boolean_list       (GDesktopEntries  *entries,
+					                       const gchar    *group,
+					                       const gchar    *key,
+					                       gsize          *length,
+							       GError        **error);
+gint                  *g_desktop_entries_get_integer_list       (GDesktopEntries  *entries,
+					                       const gchar    *group,
+					                       const gchar    *key,
+					                       gsize          *length,
+							       GError        **error);
+
+void                    g_desktop_entries_add_entry (GDesktopEntries *entries,
+				                     const gchar   *group,
+				                     const gchar   *key,
+				                     const gchar   *value);
+
+void                    g_desktop_entries_remove_entry (GDesktopEntries *entries,
+				                        const gchar   *group,
+				                        const gchar   *key);
+					      
+void                    g_desktop_entries_add_group  (GDesktopEntries *entries,
+				                      const gchar     *group_name);
+
+void                    g_desktop_entries_remove_group (GDesktopEntries *entries,
+		 		                        const gchar     *group_name);
+
+
+
+G_END_DECLS
+#endif /* __G_DESKTOP_ENTRIES_H__ */
Index: gfileutils.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gfileutils.c,v
retrieving revision 1.43
diff -p -u -u -r1.43 gfileutils.c
--- gfileutils.c	14 Dec 2003 19:05:29 -0000	1.43
+++ gfileutils.c	29 Apr 2004 22:13:06 -0000
@@ -149,11 +149,13 @@ g_file_test (const gchar *filename,
   
   if (test & (G_FILE_TEST_IS_REGULAR |
 	      G_FILE_TEST_IS_DIR |
-	      G_FILE_TEST_IS_EXECUTABLE))
+	      G_FILE_TEST_IS_EXECUTABLE |
+	      G_FILE_TEST_IS_READABLE |
+	      G_FILE_TEST_IS_WRITABLE))
     {
-      struct stat s;
+      struct stat s = { 0 };
       
-      if (stat (filename, &s) == 0)
+      if (stat (filename, &s) == 0 || (test & G_FILE_TEST_IS_WRITABLE))
 	{
 	  if ((test & G_FILE_TEST_IS_REGULAR) && S_ISREG (s.st_mode))
 	    return TRUE;
@@ -175,10 +177,110 @@ g_file_test (const gchar *filename,
 	      (s.st_mode & _S_IEXEC))
 	    return TRUE;
 #endif
+
+          if (test & (G_FILE_TEST_IS_READABLE | G_FILE_TEST_IS_WRITABLE))
+            {
+              int fd, mask;
+
+              mask = (test & G_FILE_TEST_IS_READABLE)? S_IRUSR : S_IWUSR;
+              
+              if (S_ISDIR (s.st_mode))
+                return (s.st_mode & mask) != 0;
+
+              fd = open (filename, (test & G_FILE_TEST_IS_READABLE)? 
+                                   O_RDONLY : O_WRONLY);
+
+              if (fd < 0)
+                return FALSE;
+
+              close (fd);
+
+              return TRUE;
+            }
 	}
     }
 
   return FALSE;
+}
+
+/**
+ * g_file_full_test:
+ * @filename: a filename to test
+ * @tests: bitfield of #GFileTest flags
+ * 
+ * Returns %TRUE if all of the tests in the bitfield @tests are
+ * %TRUE. For example, <literal>(G_FILE_TEST_EXISTS | 
+ * G_FILE_TEST_IS_DIR)</literal> will return %TRUE if the file exists
+ * and the file is a directory. 
+ * 
+ * Apart from %G_FILE_TEST_IS_SYMLINK all tests follow symbolic links,
+ * so for a symbolic link to a regular file g_file_full_test() will 
+ * return %TRUE for both %G_FILE_TEST_IS_SYMLINK and 
+ * %G_FILE_TEST_IS_REGULAR.
+ *
+ * Note, that for a dangling symbolic link g_file_full_test() will 
+ * return %TRUE for %G_FILE_TEST_IS_SYMLINK 
+ * (and possibly G_FILE_TEST_IS_WRITABLE) and %FALSE for all other
+ * flags.
+ *
+ * You should never use g_file_full_test() to test whether it is safe
+ * to perform an operaton, because there is always the possibility
+ * of the condition changing before you actually perform the operation.
+ * For example, you might think you could use %G_FILE_TEST_IS_SYMLINK
+ * to know whether it is is safe to write to a file without being
+ * tricked into writing into a different location. It doesn't work!
+ *
+ * <informalexample><programlisting>
+ * /&ast; DON'T DO THIS &ast;/
+ *  if (!g_file_full_test (filename, G_FILE_TEST_IS_SYMLINK)) {
+ *    fd = open (filename, O_WRONLY);
+ *    /&ast; write to fd &ast;/
+ *  }
+ * </programlisting></informalexample>
+ *
+ * Another thing to note is that %G_FILE_TEST_EXISTS and
+ * %G_FILE_TEST_IS_EXECUTABLE are implemented using the access()
+ * system call. This usually doesn't matter, but if your program
+ * is setuid or setgid it means that these tests will give you
+ * the answer for the real user ID and group ID , rather than the
+ * effective user ID and group ID.
+ *
+ * Return value: whether all tests were %TRUE
+ **/
+gboolean
+g_file_full_test (const gchar *filename,
+                  GFileTest tests)
+{
+  /* Note this array needs to be kept in sync with the enumeration 
+   * defined in the gfileutils.h
+   */
+  static const GFileTest all_tests[] = { G_FILE_TEST_IS_REGULAR, 
+                                         G_FILE_TEST_IS_SYMLINK,  
+                                         G_FILE_TEST_IS_DIR,
+                                         G_FILE_TEST_IS_EXECUTABLE,
+                                         G_FILE_TEST_EXISTS,
+                                         G_FILE_TEST_IS_READABLE,
+                                         G_FILE_TEST_IS_WRITABLE,
+                                         (GFileTest) 0 };
+  int i;
+
+  for (i = 0; tests && all_tests[i]; i++)
+    {
+      if (tests & all_tests[i])
+        {
+          if (!g_file_test (filename, all_tests[i]))
+            return FALSE;
+
+          tests &= ~all_tests[i];
+        }
+    }
+
+  /* Either the user sent an invalid bitmask or the tests array has 
+   * gotten out of sync with the GFileTest enumeration
+   */
+  g_return_val_if_fail (tests == (GFileTest) 0, FALSE);
+
+  return TRUE;
 }
 
 GQuark
Index: gfileutils.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gfileutils.h,v
retrieving revision 1.12
diff -p -u -u -r1.12 gfileutils.h
--- gfileutils.h	29 Jul 2003 22:31:39 -0000	1.12
+++ gfileutils.h	29 Apr 2004 22:13:06 -0000
@@ -65,7 +65,9 @@ typedef enum
   G_FILE_TEST_IS_SYMLINK    = 1 << 1,
   G_FILE_TEST_IS_DIR        = 1 << 2,
   G_FILE_TEST_IS_EXECUTABLE = 1 << 3,
-  G_FILE_TEST_EXISTS        = 1 << 4
+  G_FILE_TEST_EXISTS        = 1 << 4,
+  G_FILE_TEST_IS_READABLE   = 1 << 5,
+  G_FILE_TEST_IS_WRITABLE   = 1 << 6
 } GFileTest;
 
 GQuark     g_file_error_quark      (void);
@@ -74,6 +76,9 @@ GFileError g_file_error_from_errno (gint
 
 gboolean g_file_test         (const gchar  *filename,
                               GFileTest     test);
+gboolean g_file_full_test    (const gchar  *filename,
+                              GFileTest     tests);
+
 gboolean g_file_get_contents (const gchar  *filename,
                               gchar       **contents,
                               gsize        *length,    
Index: glib.def
===================================================================
RCS file: /cvs/gnome/glib/glib/glib.def,v
retrieving revision 1.112
diff -p -u -u -r1.112 glib.def
--- glib.def	5 Mar 2004 20:12:51 -0000	1.112
+++ glib.def	29 Apr 2004 22:13:06 -0000
@@ -159,6 +159,7 @@ EXPORTS
 	g_error_new_literal
 	g_file_error_from_errno
 	g_file_error_quark
+	g_file_full_test
 	g_file_get_contents
 	g_file_open_tmp
 	g_file_read_link
Index: glib.h
===================================================================
RCS file: /cvs/gnome/glib/glib/glib.h,v
retrieving revision 1.219
diff -p -u -u -r1.219 glib.h
--- glib.h	26 Feb 2004 14:30:34 -0000	1.219
+++ glib.h	29 Apr 2004 22:13:06 -0000
@@ -38,6 +38,7 @@
 #include <glib/gdataset.h>
 #include <glib/gdate.h>
 #include <glib/gdir.h>
+#include <glib/gdesktopentries.h>
 #include <glib/gerror.h>
 #include <glib/gfileutils.h>
 #include <glib/ghash.h>
Index: gutils.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gutils.c,v
retrieving revision 1.128
diff -p -u -u -r1.128 gutils.c
--- gutils.c	21 Mar 2004 21:43:11 -0000	1.128
+++ gutils.c	29 Apr 2004 22:13:09 -0000
@@ -30,6 +30,8 @@
 
 #include "config.h"
 
+#include <ctype.h>
+
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -52,6 +54,8 @@
 #define	__G_UTILS_C__
 #include "glib.h"
 #include "gprintfint.h"
+#include "gfileutils.h"
+#include "gdesktopentries.h"
 
 #ifdef	MAXPATHLEN
 #define	G_PATH_LENGTH	MAXPATHLEN
@@ -495,6 +499,233 @@ g_path_get_basename (const gchar   *file
   return retval;
 }
 
+/**
+ * g_get_user_data_dir:
+ * 
+ * Returns the data directory of the user.
+ * 
+ * Return value: the value of the data directory of the user as 
+ *               specified by the XDG Base Directory specification. 
+ **/
+gchar*
+g_get_user_data_dir (void)
+{
+  gchar *data_dir;  
+
+  data_dir = (gchar *) g_getenv ("XDG_DATA_HOME");
+
+  if (data_dir && data_dir[0])
+    data_dir = g_strdup (data_dir);
+  else
+    data_dir = g_build_filename (g_get_home_dir (), ".local", "share", NULL);
+
+  return data_dir;
+}
+
+/**
+ * g_get_user_configuration_dir:
+ * 
+ * Returns the configuration directory of the user.
+ * 
+ * Return value: the value of the configuration directory of the user
+ *               as specified by the XDG Base Directory specification. 
+ **/
+gchar*
+g_get_user_configuration_dir (void)
+{
+  gchar *conf_dir;  
+
+  conf_dir = (gchar *) g_getenv ("XDG_CONFIG_HOME");
+
+  if (conf_dir && conf_dir[0])
+    conf_dir = g_strdup (conf_dir);
+  else
+    conf_dir = g_build_filename (g_get_home_dir (), ".config", NULL);
+
+  return conf_dir;
+}
+
+/**
+ * g_get_user_cache_dir:
+ * 
+ * Returns the cache directory of the user.
+ * 
+ * Return value: the value of the cache directory of the user
+ *               as specified by the XDG Base Directory specification. 
+ **/
+gchar*
+g_get_user_cache_dir (void)
+{
+  gchar *cache_dir;  
+
+  cache_dir = (gchar *) g_getenv ("XDG_CACHE_HOME");
+
+  if (cache_dir && cache_dir[0])
+    cache_dir = g_strdup (cache_dir);
+  else
+    cache_dir = g_build_filename (g_get_home_dir (), ".cache", NULL);
+
+  return cache_dir;
+}
+
+/**
+ * g_get_secondary_data_dirs:
+ * 
+ * Returns a %NULL-terminated array of preference ordered data directories.
+ * 
+ * Return value: a %NULL-terminated array of preference ordered data 
+ *               directories as specified by the XDG Base Directory 
+ *               specification. Use g_strfreev to free it.
+ **/
+gchar**
+g_get_secondary_data_dirs (void)
+{
+  gchar *data_dirs, **data_dir_vector;
+
+  data_dirs = (gchar *) g_getenv ("XDG_DATA_DIRS");
+
+  if (!data_dirs || !data_dirs[0])
+    data_dirs = "/usr/local/share/:/usr/share/";
+
+  data_dir_vector = g_strsplit (data_dirs, ":", 0);
+
+  return data_dir_vector;
+}
+
+/**
+ * g_get_secondary_configuration_dirs:
+ * 
+ * Returns a %NULL-terminated array of preference ordered configuration
+ * directories.
+ * 
+ * Return value: a %NULL-terminated array of preference ordered 
+ *               configuration directories as specified by the XDG 
+ *               Base Directory specification. Use g_strfreev to free 
+ *               it.
+ **/
+gchar**
+g_get_secondary_configuration_dirs (void)
+{
+  gchar *conf_dirs, **conf_dir_vector;
+
+  conf_dirs = (gchar *) g_getenv ("XDG_CONFIG_DIRS");
+
+  if (!conf_dirs || !conf_dirs[0])
+    conf_dirs = "/etc/xdg";
+
+  conf_dir_vector = g_strsplit (conf_dirs, ":", 0);
+
+  return conf_dir_vector;
+}
+
+/**
+ * g_find_file_in_data_dir:
+ * @file: the relative filename to look for
+ * @tests: bitfield of #GFileTest flags
+ * 
+ * Looks for a file named @file that passes all @tests.  
+ * g_find_file_in_data_dir first checks in the path returned by 
+ * g_get_user_data_dir and then checks the paths returned by
+ * g_get_secondary_data_dirs.
+ * 
+ * Return value: the full path of file named @file that passes all @tests or
+ *               %NULL if no file could be found.
+ **/
+gchar*
+g_find_file_in_data_dir (const gchar *file, 
+                         GFileTest    tests)
+{
+  gchar *data_dir, *path, **secondary_data_dirs;
+  int i;
+
+  path = NULL;
+
+  data_dir = g_get_user_data_dir ();
+
+  if (data_dir) 
+    {
+      path = g_build_filename (data_dir, file, NULL);
+      g_free (data_dir);
+      data_dir = NULL;
+    }
+
+  if (path && g_file_full_test (path, tests))
+    return path;
+
+  secondary_data_dirs = g_get_secondary_data_dirs ();
+
+  i = 0;
+  while (secondary_data_dirs && (data_dir = secondary_data_dirs[i]))
+      {
+        path = g_build_filename (data_dir, file, NULL);
+
+        if (g_file_full_test (path, tests))
+          break;
+
+        g_free (path);
+        path = NULL;
+        i++;
+      }
+
+  g_strfreev (secondary_data_dirs);
+
+  return path;
+}
+
+/**
+ * g_find_file_in_configuration_dir:
+ * @file: the relative filename to look for
+ * @tests: bitfield of #GFileTest flags
+ * 
+ * Looks for a file named @file that passes all @tests.  
+ * g_find_file_in_data_dir first checks in the path returned by 
+ * g_get_user_configuration_dir and then checks the paths returned by
+ * g_get_secondary_configuration_dirs.
+ * 
+ * Return value: the full path of file named @file that passes all @tests or
+ *               %NULL if no file could be found.
+ **/
+gchar*
+g_find_file_in_configuration_dir (const gchar *file, 
+                                  GFileTest    tests)
+{
+  gchar *conf_dir, *path, **secondary_conf_dirs;
+  int i;
+
+  path = NULL;
+
+  conf_dir = g_get_user_configuration_dir ();
+
+  if (conf_dir) 
+    {
+      path = g_build_filename (conf_dir, file, NULL);
+      g_free (conf_dir);
+      conf_dir = NULL;
+    }
+
+  if (path && g_file_full_test (path, tests))
+    return path;
+
+  secondary_conf_dirs = g_get_secondary_configuration_dirs ();
+
+  i = 0;
+  while (secondary_conf_dirs && (conf_dir = secondary_conf_dirs[i]))
+      {
+        path = g_build_filename (conf_dir, file, NULL);
+
+        if (g_file_full_test (path, tests))
+          break;
+
+        g_free (path);
+        path = NULL;
+        i++;
+      }
+
+  g_strfreev (secondary_conf_dirs);
+
+  return path;
+}
+
 gboolean
 g_path_is_absolute (const gchar *file_name)
 {
@@ -1177,6 +1408,111 @@ g_set_application_name (const gchar *app
 
   if (already_set)
     g_warning ("g_set_application() name called multiple times");
+}
+
+G_LOCK_DEFINE_STATIC (g_desktop_entries_global);
+static GDesktopEntries *g_desktop_entries = NULL;
+
+/**
+ * g_set_desktop_file:
+ * @file: The path to a .desktop file
+ * 
+ * Associates the application with a .desktop file.  The information
+ * in the .desktop file is used for describing the application. @file
+ * can be an absolute path or a path relative to a data dir (see
+ * g_find_file_in_data_dir()).  This function creates a #GDesktopEntries 
+ * which can be retrieved with g_get_desktop_entries().
+ * 
+ * Since: 2.6
+ **/
+void
+g_set_desktop_entries_from_file (const gchar *file)
+{
+  GDesktopEntries *entries;
+
+  entries = g_desktop_entries_new_from_file (G_DESKTOP_ENTRIES_KEEP_TRANSLATIONS, file, NULL);
+
+  if (entries)
+    g_set_desktop_entries (entries);
+}
+
+/**
+ * g_get_desktop_entries:
+ * 
+ * Retrieves the desktop entries used to parse the .desktop file associated 
+ * with the application as set by g_set_desktop_file() or 
+ * g_set_desktop_entries()
+ * 
+ * Return value: a #GDesktopEntries
+ *
+ * Since: 2.6
+ **/
+G_CONST_RETURN GDesktopEntries *
+g_get_desktop_entries ()
+{
+  const GDesktopEntries *retval;
+
+  G_LOCK (g_desktop_entries_global);
+  retval = g_desktop_entries;
+  G_UNLOCK (g_desktop_entries_global);
+
+  return retval;
+}
+
+/**
+ * g_set_desktop_entries:
+ * @entries: a #GDesktopEntries
+ * 
+ * Associates a #GDesktopEntries with the application. 
+ * The information in the desktop entries is used for 
+ * describing the application. 
+ * 
+ * Since: 2.6
+ **/
+void
+g_set_desktop_entries (GDesktopEntries *entries)
+{
+  gchar *name;
+
+  name = NULL;
+
+  G_LOCK (g_desktop_entries_global);
+
+  if (g_desktop_entries)
+    g_desktop_entries_free (g_desktop_entries);
+  g_desktop_entries = entries;
+
+  if (g_desktop_entries)
+    {
+      name = g_desktop_entries_get_locale_string (g_desktop_entries, 
+                                                 G_DESKTOP_ENTRIES_DEFAULT_GROUP,
+                                                 "Name", NULL, NULL);
+      if (name)
+        {
+          g_set_application_name (name);
+          g_free (name);
+          name = NULL;
+        }
+
+      name = g_desktop_entries_get_string (g_desktop_entries,
+                                           G_DESKTOP_ENTRIES_DEFAULT_GROUP,
+                                           "Exec", NULL);
+
+      if (name)
+        {
+          gchar *p;
+
+          p = name;
+          while (*p && !isspace (*p))
+            p++;
+          *p = '\0';
+
+          g_set_prgname (name);
+          g_free (name);
+          name = NULL;
+        }
+    }
+  G_UNLOCK (g_desktop_entries_global);
 }
 
 guint
Index: gutils.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gutils.h,v
retrieving revision 1.18
diff -p -u -u -r1.18 gutils.h
--- gutils.h	10 Jan 2004 08:15:23 -0000	1.18
+++ gutils.h	29 Apr 2004 22:13:09 -0000
@@ -28,6 +28,8 @@
 #define __G_UTILS_H__
 
 #include <glib/gtypes.h>
+#include <glib/gfileutils.h>
+#include <glib/gdesktopentries.h>
 #include <stdarg.h>
 
 G_BEGIN_DECLS
@@ -121,6 +123,9 @@ gchar*                g_get_prgname     
 void                  g_set_prgname          (const gchar *prgname);
 G_CONST_RETURN gchar* g_get_application_name (void);
 void                  g_set_application_name (const gchar *application_name);
+void                  g_set_desktop_entries_from_file     (const gchar *file);
+void                  g_set_desktop_entries   (GDesktopEntries *entries);
+G_CONST_RETURN GDesktopEntries* g_get_desktop_entries ();
 
 
 typedef struct _GDebugKey	GDebugKey;
@@ -167,6 +172,16 @@ gchar*                g_get_current_dir 
 gchar*                g_path_get_basename  (const gchar *file_name);
 gchar*                g_path_get_dirname   (const gchar *file_name);
 
+gchar*                g_get_user_data_dir                (void);
+gchar*                g_get_user_configuration_dir       (void);
+gchar*                g_get_user_cache_dir               (void);
+gchar**               g_get_secondary_data_dirs          (void);
+gchar**               g_get_secondary_configuration_dirs (void);
+
+gchar*                g_find_file_in_data_dir          (const gchar *file, 
+                                                        GFileTest tests);
+gchar*                g_find_file_in_configuration_dir (const gchar *file,
+                                                        GFileTest tests);
 
 /* Set the pointer at the specified location to NULL */
 void                  g_nullify_pointer    (gpointer    *nullify_location);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]