bookmarkfile: Fix annotations on GBookmarkFile
[platform/upstream/glib.git] / glib / gbookmarkfile.c
1 /* gbookmarkfile.c: parsing and building desktop bookmarks
2  *
3  * Copyright (C) 2005-2006 Emmanuele Bassi
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  */
19
20 #include "config.h"
21
22 #include "gbookmarkfile.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <locale.h>
30 #include <time.h>
31 #include <stdarg.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37
38 #include "gconvert.h"
39 #include "gdataset.h"
40 #include "gerror.h"
41 #include "gfileutils.h"
42 #include "ghash.h"
43 #include "glibintl.h"
44 #include "glist.h"
45 #include "gslist.h"
46 #include "gmain.h"
47 #include "gmarkup.h"
48 #include "gmem.h"
49 #include "gmessages.h"
50 #include "gshell.h"
51 #include "gslice.h"
52 #include "gstdio.h"
53 #include "gstring.h"
54 #include "gstrfuncs.h"
55 #include "gtimer.h"
56 #include "gutils.h"
57
58
59 /**
60  * SECTION:bookmarkfile
61  * @title: Bookmark file parser
62  * @short_description: parses files containing bookmarks
63  *
64  * GBookmarkFile lets you parse, edit or create files containing bookmarks
65  * to URI, along with some meta-data about the resource pointed by the URI
66  * like its MIME type, the application that is registering the bookmark and
67  * the icon that should be used to represent the bookmark. The data is stored
68  * using the
69  * <ulink url="http://www.gnome.org/~ebassi/bookmark-spec">Desktop Bookmark
70  * Specification</ulink>.
71  *
72  * The syntax of the bookmark files is described in detail inside the Desktop
73  * Bookmark Specification, here is a quick summary: bookmark files use a
74  * sub-class of the <ulink url="">XML Bookmark Exchange Language</ulink>
75  * specification, consisting of valid UTF-8 encoded XML, under the
76  * <literal>xbel</literal> root element; each bookmark is stored inside a
77  * <literal>bookmark</literal> element, using its URI: no relative paths can
78  * be used inside a bookmark file. The bookmark may have a user defined title
79  * and description, to be used instead of the URI. Under the
80  * <literal>metadata</literal> element, with its <literal>owner</literal>
81  * attribute set to <literal>http://freedesktop.org</literal>, is stored the
82  * meta-data about a resource pointed by its URI. The meta-data consists of
83  * the resource's MIME type; the applications that have registered a bookmark;
84  * the groups to which a bookmark belongs to; a visibility flag, used to set
85  * the bookmark as "private" to the applications and groups that has it
86  * registered; the URI and MIME type of an icon, to be used when displaying
87  * the bookmark inside a GUI.
88  * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../glib/tests/bookmarks.xbel"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
89  *
90  * A bookmark file might contain more than one bookmark; each bookmark
91  * is accessed through its URI.
92  *
93  * The important caveat of bookmark files is that when you add a new
94  * bookmark you must also add the application that is registering it, using
95  * g_bookmark_file_add_application() or g_bookmark_file_set_app_info().
96  * If a bookmark has no applications then it won't be dumped when creating
97  * the on disk representation, using g_bookmark_file_to_data() or
98  * g_bookmark_file_to_file().
99  *
100  * The #GBookmarkFile parser was added in GLib 2.12.
101  */
102
103 /* XBEL 1.0 standard entities */
104 #define XBEL_VERSION            "1.0"
105 #define XBEL_DTD_NICK           "xbel"
106 #define XBEL_DTD_SYSTEM         "+//IDN python.org//DTD XML Bookmark " \
107                                 "Exchange Language 1.0//EN//XML"
108
109 #define XBEL_DTD_URI            "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd"
110
111 #define XBEL_ROOT_ELEMENT       "xbel"
112 #define XBEL_FOLDER_ELEMENT     "folder"        /* unused */
113 #define XBEL_BOOKMARK_ELEMENT   "bookmark"
114 #define XBEL_ALIAS_ELEMENT      "alias"         /* unused */
115 #define XBEL_SEPARATOR_ELEMENT  "separator"     /* unused */
116 #define XBEL_TITLE_ELEMENT      "title"
117 #define XBEL_DESC_ELEMENT       "desc"
118 #define XBEL_INFO_ELEMENT       "info"
119 #define XBEL_METADATA_ELEMENT   "metadata"
120
121 #define XBEL_VERSION_ATTRIBUTE  "version"
122 #define XBEL_FOLDED_ATTRIBUTE   "folded"        /* unused */
123 #define XBEL_OWNER_ATTRIBUTE    "owner"
124 #define XBEL_ADDED_ATTRIBUTE    "added"
125 #define XBEL_VISITED_ATTRIBUTE  "visited"
126 #define XBEL_MODIFIED_ATTRIBUTE "modified"
127 #define XBEL_ID_ATTRIBUTE       "id"
128 #define XBEL_HREF_ATTRIBUTE     "href"
129 #define XBEL_REF_ATTRIBUTE      "ref"           /* unused */
130
131 #define XBEL_YES_VALUE          "yes"
132 #define XBEL_NO_VALUE           "no"
133
134 /* Desktop bookmark spec entities */
135 #define BOOKMARK_METADATA_OWNER         "http://freedesktop.org"
136
137 #define BOOKMARK_NAMESPACE_NAME         "bookmark"
138 #define BOOKMARK_NAMESPACE_URI          "http://www.freedesktop.org/standards/desktop-bookmarks"
139
140 #define BOOKMARK_GROUPS_ELEMENT         "groups"
141 #define BOOKMARK_GROUP_ELEMENT          "group"
142 #define BOOKMARK_APPLICATIONS_ELEMENT   "applications"
143 #define BOOKMARK_APPLICATION_ELEMENT    "application"
144 #define BOOKMARK_ICON_ELEMENT           "icon"
145 #define BOOKMARK_PRIVATE_ELEMENT        "private"
146
147 #define BOOKMARK_NAME_ATTRIBUTE         "name"
148 #define BOOKMARK_EXEC_ATTRIBUTE         "exec"
149 #define BOOKMARK_COUNT_ATTRIBUTE        "count"
150 #define BOOKMARK_TIMESTAMP_ATTRIBUTE    "timestamp"     /* deprecated by "modified" */
151 #define BOOKMARK_MODIFIED_ATTRIBUTE     "modified"
152 #define BOOKMARK_HREF_ATTRIBUTE         "href"
153 #define BOOKMARK_TYPE_ATTRIBUTE         "type"
154
155 /* Shared MIME Info entities */
156 #define MIME_NAMESPACE_NAME             "mime"
157 #define MIME_NAMESPACE_URI              "http://www.freedesktop.org/standards/shared-mime-info"
158 #define MIME_TYPE_ELEMENT               "mime-type"
159 #define MIME_TYPE_ATTRIBUTE             "type"
160
161
162 typedef struct _BookmarkAppInfo  BookmarkAppInfo;
163 typedef struct _BookmarkMetadata BookmarkMetadata;
164 typedef struct _BookmarkItem     BookmarkItem;
165 typedef struct _ParseData        ParseData;
166
167 struct _BookmarkAppInfo
168 {
169   gchar *name;
170   gchar *exec;
171   
172   guint count;
173   
174   time_t stamp;
175 };
176
177 struct _BookmarkMetadata
178 {
179   gchar *mime_type;
180   
181   GList *groups;
182   
183   GList *applications;
184   GHashTable *apps_by_name;
185   
186   gchar *icon_href;
187   gchar *icon_mime;
188   
189   guint is_private : 1;
190 };
191
192 struct _BookmarkItem
193 {
194   gchar *uri;
195
196   gchar *title;
197   gchar *description;
198
199   time_t added;
200   time_t modified;
201   time_t visited;
202
203   BookmarkMetadata *metadata;
204 };
205
206 struct _GBookmarkFile
207 {
208   gchar *title;
209   gchar *description;
210
211   /* we store our items in a list and keep a copy inside
212    * an hash table for faster lookup performances
213    */
214   GList *items;
215   GHashTable *items_by_uri;
216 };
217
218 /* parser state machine */
219 enum
220 {
221   STATE_STARTED        = 0,
222   
223   STATE_ROOT,
224   STATE_BOOKMARK,
225   STATE_TITLE,
226   STATE_DESC,
227   STATE_INFO,
228   STATE_METADATA,
229   STATE_APPLICATIONS,
230   STATE_APPLICATION,
231   STATE_GROUPS,
232   STATE_GROUP,
233   STATE_MIME,
234   STATE_ICON,
235   
236   STATE_FINISHED
237 };
238
239 static void          g_bookmark_file_init        (GBookmarkFile  *bookmark);
240 static void          g_bookmark_file_clear       (GBookmarkFile  *bookmark);
241 static gboolean      g_bookmark_file_parse       (GBookmarkFile  *bookmark,
242                                                   const gchar    *buffer,
243                                                   gsize           length,
244                                                   GError        **error);
245 static gchar *       g_bookmark_file_dump        (GBookmarkFile  *bookmark,
246                                                   gsize          *length,
247                                                   GError        **error);
248 static BookmarkItem *g_bookmark_file_lookup_item (GBookmarkFile  *bookmark,
249                                                   const gchar    *uri);
250 static void          g_bookmark_file_add_item    (GBookmarkFile  *bookmark,
251                                                   BookmarkItem   *item,
252                                                   GError        **error);
253                                                        
254 static time_t  timestamp_from_iso8601 (const gchar *iso_date);
255 static gchar * timestamp_to_iso8601   (time_t       timestamp);
256
257 /********************************
258  * BookmarkAppInfo              *
259  *                              *
260  * Application metadata storage *
261  ********************************/
262 static BookmarkAppInfo *
263 bookmark_app_info_new (const gchar *name)
264 {
265   BookmarkAppInfo *retval;
266  
267   g_warn_if_fail (name != NULL);
268   
269   retval = g_slice_new (BookmarkAppInfo);
270   
271   retval->name = g_strdup (name);
272   retval->exec = NULL;
273   retval->count = 0;
274   retval->stamp = 0;
275   
276   return retval;
277 }
278
279 static void
280 bookmark_app_info_free (BookmarkAppInfo *app_info)
281 {
282   if (!app_info)
283     return;
284   
285   g_free (app_info->name);
286   g_free (app_info->exec);
287   
288   g_slice_free (BookmarkAppInfo, app_info);
289 }
290
291 static gchar *
292 bookmark_app_info_dump (BookmarkAppInfo *app_info)
293 {
294   gchar *retval;
295   gchar *name, *exec, *modified, *count;
296
297   g_warn_if_fail (app_info != NULL);
298
299   if (app_info->count == 0)
300     return NULL;
301
302   name = g_markup_escape_text (app_info->name, -1);
303   exec = g_markup_escape_text (app_info->exec, -1);
304   modified = timestamp_to_iso8601 (app_info->stamp);
305   count = g_strdup_printf ("%u", app_info->count);
306
307   retval = g_strconcat ("          "
308                         "<" BOOKMARK_NAMESPACE_NAME ":" BOOKMARK_APPLICATION_ELEMENT
309                         " " BOOKMARK_NAME_ATTRIBUTE "=\"", name, "\""
310                         " " BOOKMARK_EXEC_ATTRIBUTE "=\"", exec, "\""
311                         " " BOOKMARK_MODIFIED_ATTRIBUTE "=\"", modified, "\""
312                         " " BOOKMARK_COUNT_ATTRIBUTE "=\"", count, "\"/>\n",
313                         NULL);
314
315   g_free (name);
316   g_free (exec);
317   g_free (modified);
318   g_free (count);
319
320   return retval;
321 }
322
323
324 /***********************
325  * BookmarkMetadata    *
326  *                     *
327  * Metadata storage    *
328  ***********************/
329 static BookmarkMetadata *
330 bookmark_metadata_new (void)
331 {
332   BookmarkMetadata *retval;
333   
334   retval = g_slice_new (BookmarkMetadata);
335
336   retval->mime_type = NULL;
337   
338   retval->groups = NULL;
339   
340   retval->applications = NULL;
341   retval->apps_by_name = g_hash_table_new_full (g_str_hash,
342                                                 g_str_equal,
343                                                 NULL,
344                                                 NULL);
345   
346   retval->is_private = FALSE;
347   
348   retval->icon_href = NULL;
349   retval->icon_mime = NULL;
350   
351   return retval;
352 }
353
354 static void
355 bookmark_metadata_free (BookmarkMetadata *metadata)
356 {
357   if (!metadata)
358     return;
359   
360   g_free (metadata->mime_type);
361     
362   g_list_free_full (metadata->groups, g_free);
363   g_list_free_full (metadata->applications, (GDestroyNotify) bookmark_app_info_free);
364
365   g_hash_table_destroy (metadata->apps_by_name);
366
367   g_free (metadata->icon_href);
368   g_free (metadata->icon_mime);
369   
370   g_slice_free (BookmarkMetadata, metadata);
371 }
372
373 static gchar *
374 bookmark_metadata_dump (BookmarkMetadata *metadata)
375 {
376   GString *retval;
377   gchar *buffer;
378  
379   if (!metadata->applications)
380     return NULL;
381   
382   retval = g_string_sized_new (1024);
383   
384   /* metadata container */
385   g_string_append (retval,
386                    "      "
387                    "<" XBEL_METADATA_ELEMENT
388                    " " XBEL_OWNER_ATTRIBUTE "=\"" BOOKMARK_METADATA_OWNER
389                    "\">\n");
390
391   /* mime type */
392   if (metadata->mime_type) {
393     buffer = g_strconcat ("        "
394                           "<" MIME_NAMESPACE_NAME ":" MIME_TYPE_ELEMENT " "
395                           MIME_TYPE_ATTRIBUTE "=\"", metadata->mime_type, "\"/>\n",
396                           NULL);    
397     g_string_append (retval, buffer);
398     g_free (buffer);
399   }
400
401   if (metadata->groups)
402     {
403       GList *l;
404       
405       /* open groups container */
406       g_string_append (retval,
407                        "        "
408                        "<" BOOKMARK_NAMESPACE_NAME
409                        ":" BOOKMARK_GROUPS_ELEMENT ">\n");
410       
411       for (l = g_list_last (metadata->groups); l != NULL; l = l->prev)
412         {
413           gchar *group_name;
414
415           group_name = g_markup_escape_text ((gchar *) l->data, -1);
416           buffer = g_strconcat ("          "
417                                 "<" BOOKMARK_NAMESPACE_NAME
418                                 ":" BOOKMARK_GROUP_ELEMENT ">",
419                                 group_name,
420                                 "</" BOOKMARK_NAMESPACE_NAME
421                                 ":"  BOOKMARK_GROUP_ELEMENT ">\n", NULL);
422           g_string_append (retval, buffer);
423
424           g_free (buffer);
425           g_free (group_name);
426         }
427       
428       /* close groups container */
429       g_string_append (retval,
430                        "        "
431                        "</" BOOKMARK_NAMESPACE_NAME
432                        ":" BOOKMARK_GROUPS_ELEMENT ">\n");
433     }
434   
435   if (metadata->applications)
436     {
437       GList *l;
438       
439       /* open applications container */
440       g_string_append (retval,
441                        "        "
442                        "<" BOOKMARK_NAMESPACE_NAME
443                        ":" BOOKMARK_APPLICATIONS_ELEMENT ">\n");
444       
445       for (l = g_list_last (metadata->applications); l != NULL; l = l->prev)
446         {
447           BookmarkAppInfo *app_info = (BookmarkAppInfo *) l->data;
448           gchar *app_data;
449
450           g_warn_if_fail (app_info != NULL);
451           
452           app_data = bookmark_app_info_dump (app_info);
453
454           if (app_data)
455             {
456               retval = g_string_append (retval, app_data);
457
458               g_free (app_data);
459             }
460         }
461       
462       /* close applications container */
463       g_string_append (retval,
464                        "        "
465                        "</" BOOKMARK_NAMESPACE_NAME
466                        ":" BOOKMARK_APPLICATIONS_ELEMENT ">\n");
467     }
468   
469   /* icon */
470   if (metadata->icon_href)
471     {
472       if (!metadata->icon_mime)
473         metadata->icon_mime = g_strdup ("application/octet-stream");
474
475       buffer = g_strconcat ("       "
476                             "<" BOOKMARK_NAMESPACE_NAME
477                             ":" BOOKMARK_ICON_ELEMENT
478                             " " BOOKMARK_HREF_ATTRIBUTE "=\"", metadata->icon_href,
479                             "\" " BOOKMARK_TYPE_ATTRIBUTE "=\"", metadata->icon_mime, "\"/>\n", NULL);
480       g_string_append (retval, buffer);
481
482       g_free (buffer);
483     }
484   
485   /* private hint */
486   if (metadata->is_private)
487     g_string_append (retval,
488                      "        "
489                      "<" BOOKMARK_NAMESPACE_NAME
490                      ":" BOOKMARK_PRIVATE_ELEMENT "/>\n");
491   
492   /* close metadata container */
493   g_string_append (retval,
494                    "      "
495                    "</" XBEL_METADATA_ELEMENT ">\n");
496                            
497   return g_string_free (retval, FALSE);
498 }
499
500 /******************************************************
501  * BookmarkItem                                       *
502  *                                                    *
503  * Storage for a single bookmark item inside the list *
504  ******************************************************/
505 static BookmarkItem *
506 bookmark_item_new (const gchar *uri)
507 {
508   BookmarkItem *item;
509  
510   g_warn_if_fail (uri != NULL);
511   
512   item = g_slice_new (BookmarkItem);
513   item->uri = g_strdup (uri);
514   
515   item->title = NULL;
516   item->description = NULL;
517   
518   item->added = (time_t) -1;
519   item->modified = (time_t) -1;
520   item->visited = (time_t) -1;
521   
522   item->metadata = NULL;
523   
524   return item;
525 }
526
527 static void
528 bookmark_item_free (BookmarkItem *item)
529 {
530   if (!item)
531     return;
532
533   g_free (item->uri);
534   g_free (item->title);
535   g_free (item->description);
536   
537   if (item->metadata)
538     bookmark_metadata_free (item->metadata);
539   
540   g_slice_free (BookmarkItem, item);
541 }
542
543 static gchar *
544 bookmark_item_dump (BookmarkItem *item)
545 {
546   GString *retval;
547   gchar *added, *visited, *modified;
548   gchar *escaped_uri;
549   gchar *buffer;
550  
551   /* at this point, we must have at least a registered application; if we don't
552    * we don't screw up the bookmark file, and just skip this item
553    */
554   if (!item->metadata || !item->metadata->applications)
555     {
556       g_warning ("Item for URI '%s' has no registered applications: skipping.\n", item->uri);
557       return NULL;
558     }
559   
560   retval = g_string_sized_new (4096);
561   
562   added = timestamp_to_iso8601 (item->added);
563   modified = timestamp_to_iso8601 (item->modified);
564   visited = timestamp_to_iso8601 (item->visited);
565
566   escaped_uri = g_markup_escape_text (item->uri, -1);
567
568   buffer = g_strconcat ("  <"
569                         XBEL_BOOKMARK_ELEMENT
570                         " "
571                         XBEL_HREF_ATTRIBUTE "=\"", escaped_uri, "\" "
572                         XBEL_ADDED_ATTRIBUTE "=\"", added, "\" "
573                         XBEL_MODIFIED_ATTRIBUTE "=\"", modified, "\" "
574                         XBEL_VISITED_ATTRIBUTE "=\"", visited, "\">\n",
575                         NULL);
576
577   g_string_append (retval, buffer);
578
579   g_free (escaped_uri);
580   g_free (visited);
581   g_free (modified);
582   g_free (added);
583   g_free (buffer);
584   
585   if (item->title)
586     {
587       gchar *escaped_title;
588       
589       escaped_title = g_markup_escape_text (item->title, -1);
590       buffer = g_strconcat ("    "
591                             "<" XBEL_TITLE_ELEMENT ">",
592                             escaped_title,
593                             "</" XBEL_TITLE_ELEMENT ">\n",
594                             NULL);
595       g_string_append (retval, buffer);
596
597       g_free (escaped_title);
598       g_free (buffer);
599     }
600   
601   if (item->description)
602     {
603       gchar *escaped_desc;
604       
605       escaped_desc = g_markup_escape_text (item->description, -1);
606       buffer = g_strconcat ("    "
607                             "<" XBEL_DESC_ELEMENT ">",
608                             escaped_desc,
609                             "</" XBEL_DESC_ELEMENT ">\n",
610                             NULL);
611       g_string_append (retval, buffer);
612
613       g_free (escaped_desc);
614       g_free (buffer);
615     }
616   
617   if (item->metadata)
618     {
619       gchar *metadata;
620       
621       metadata = bookmark_metadata_dump (item->metadata);
622       if (metadata)
623         {
624           buffer = g_strconcat ("    "
625                                 "<" XBEL_INFO_ELEMENT ">\n",
626                                 metadata,
627                                 "    "
628                                 "</" XBEL_INFO_ELEMENT ">\n",
629                                 NULL);
630           retval = g_string_append (retval, buffer);
631
632           g_free (buffer);
633           g_free (metadata);
634         }
635     }
636
637   g_string_append (retval, "  </" XBEL_BOOKMARK_ELEMENT ">\n");
638   
639   return g_string_free (retval, FALSE);
640 }
641
642 static BookmarkAppInfo *
643 bookmark_item_lookup_app_info (BookmarkItem *item,
644                                const gchar  *app_name)
645 {
646   g_warn_if_fail (item != NULL && app_name != NULL);
647
648   if (!item->metadata)
649     return NULL;
650   
651   return g_hash_table_lookup (item->metadata->apps_by_name, app_name);
652 }
653
654 /*************************
655  *    GBookmarkFile    *
656  *************************/
657  
658 static void
659 g_bookmark_file_init (GBookmarkFile *bookmark)
660 {
661   bookmark->title = NULL;
662   bookmark->description = NULL;
663   
664   bookmark->items = NULL;
665   bookmark->items_by_uri = g_hash_table_new_full (g_str_hash,
666                                                   g_str_equal,
667                                                   NULL,
668                                                   NULL);
669 }
670
671 static void
672 g_bookmark_file_clear (GBookmarkFile *bookmark)
673 {
674   g_free (bookmark->title);
675   g_free (bookmark->description);
676
677   g_list_free_full (bookmark->items, (GDestroyNotify) bookmark_item_free);
678   bookmark->items = NULL;
679   
680   if (bookmark->items_by_uri)
681     {
682       g_hash_table_destroy (bookmark->items_by_uri);
683       
684       bookmark->items_by_uri = NULL;
685     }
686 }
687
688 struct _ParseData
689 {
690   gint state;
691   
692   GHashTable *namespaces;
693   
694   GBookmarkFile *bookmark_file;
695   BookmarkItem *current_item;
696 };
697
698 static ParseData *
699 parse_data_new (void)
700 {
701   ParseData *retval;
702   
703   retval = g_new (ParseData, 1);
704   
705   retval->state = STATE_STARTED;
706   retval->namespaces = g_hash_table_new_full (g_str_hash, g_str_equal,
707                                               (GDestroyNotify) g_free,
708                                               (GDestroyNotify) g_free);
709   retval->bookmark_file = NULL;
710   retval->current_item = NULL;
711   
712   return retval;
713 }
714
715 static void
716 parse_data_free (ParseData *parse_data)
717 {
718   g_hash_table_destroy (parse_data->namespaces);
719   
720   g_free (parse_data);
721 }
722
723 #define IS_ATTRIBUTE(s,a)       ((0 == strcmp ((s), (a))))
724
725 static void
726 parse_bookmark_element (GMarkupParseContext  *context,
727                         ParseData            *parse_data,
728                         const gchar         **attribute_names,
729                         const gchar         **attribute_values,
730                         GError              **error)
731 {
732   const gchar *uri, *added, *modified, *visited;
733   const gchar *attr;
734   gint i;
735   BookmarkItem *item;
736   GError *add_error;
737  
738   g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_BOOKMARK));
739   
740   i = 0;
741   uri = added = modified = visited = NULL;
742   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
743     {
744       if (IS_ATTRIBUTE (attr, XBEL_HREF_ATTRIBUTE))
745         uri = attribute_values[i];
746       else if (IS_ATTRIBUTE (attr, XBEL_ADDED_ATTRIBUTE))
747         added = attribute_values[i];
748       else if (IS_ATTRIBUTE (attr, XBEL_MODIFIED_ATTRIBUTE))
749         modified = attribute_values[i];
750       else if (IS_ATTRIBUTE (attr, XBEL_VISITED_ATTRIBUTE))
751         visited = attribute_values[i];
752       else
753         {
754           /* bookmark is defined by the XBEL spec, so we need
755            * to error out if the element has different or
756            * missing attributes
757            */
758           g_set_error (error, G_MARKUP_ERROR,
759                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
760                        _("Unexpected attribute '%s' for element '%s'"),
761                        attr,
762                        XBEL_BOOKMARK_ELEMENT);
763           return;
764         }
765     }
766   
767   if (!uri)
768     {
769       g_set_error (error, G_MARKUP_ERROR,
770                    G_MARKUP_ERROR_INVALID_CONTENT,
771                    _("Attribute '%s' of element '%s' not found"),
772                    XBEL_HREF_ATTRIBUTE,
773                    XBEL_BOOKMARK_ELEMENT);
774       return;
775     }
776   
777   g_warn_if_fail (parse_data->current_item == NULL);
778   
779   item = bookmark_item_new (uri);
780   
781   if (added)
782     item->added = timestamp_from_iso8601 (added);
783   
784   if (modified)
785     item->modified = timestamp_from_iso8601 (modified);
786   
787   if (visited)
788     item->visited = timestamp_from_iso8601 (visited);
789
790   add_error = NULL;
791   g_bookmark_file_add_item (parse_data->bookmark_file,
792                             item,
793                             &add_error);
794   if (add_error)
795     {
796       bookmark_item_free (item);
797       
798       g_propagate_error (error, add_error);
799       
800       return;
801     }                         
802   
803   parse_data->current_item = item;
804 }
805
806 static void
807 parse_application_element (GMarkupParseContext  *context,
808                            ParseData            *parse_data,
809                            const gchar         **attribute_names,
810                            const gchar         **attribute_values,
811                            GError              **error)
812 {
813   const gchar *name, *exec, *count, *stamp, *modified;
814   const gchar *attr;
815   gint i;
816   BookmarkItem *item;
817   BookmarkAppInfo *ai;
818   
819   g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_APPLICATION));
820
821   i = 0;
822   name = exec = count = stamp = modified = NULL;
823   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
824     {
825       if (IS_ATTRIBUTE (attr, BOOKMARK_NAME_ATTRIBUTE))
826         name = attribute_values[i];
827       else if (IS_ATTRIBUTE (attr, BOOKMARK_EXEC_ATTRIBUTE))
828         exec = attribute_values[i];
829       else if (IS_ATTRIBUTE (attr, BOOKMARK_COUNT_ATTRIBUTE))
830         count = attribute_values[i];
831       else if (IS_ATTRIBUTE (attr, BOOKMARK_TIMESTAMP_ATTRIBUTE))
832         stamp = attribute_values[i];
833       else if (IS_ATTRIBUTE (attr, BOOKMARK_MODIFIED_ATTRIBUTE))
834         modified = attribute_values[i];
835     }
836
837   /* the "name" and "exec" attributes are mandatory */
838   if (!name)
839     {
840       g_set_error (error, G_MARKUP_ERROR,
841                    G_MARKUP_ERROR_INVALID_CONTENT,
842                    _("Attribute '%s' of element '%s' not found"),
843                    BOOKMARK_NAME_ATTRIBUTE,
844                    BOOKMARK_APPLICATION_ELEMENT);
845       return;
846     }
847   
848   if (!exec)
849     {
850       g_set_error (error, G_MARKUP_ERROR,
851                    G_MARKUP_ERROR_INVALID_CONTENT,
852                    _("Attribute '%s' of element '%s' not found"),
853                    BOOKMARK_EXEC_ATTRIBUTE,
854                    BOOKMARK_APPLICATION_ELEMENT);
855       return;
856     }
857
858   g_warn_if_fail (parse_data->current_item != NULL);  
859   item = parse_data->current_item;
860     
861   ai = bookmark_item_lookup_app_info (item, name);
862   if (!ai)
863     {
864       ai = bookmark_app_info_new (name);
865       
866       if (!item->metadata)
867         item->metadata = bookmark_metadata_new ();
868       
869       item->metadata->applications = g_list_prepend (item->metadata->applications, ai);
870       g_hash_table_replace (item->metadata->apps_by_name, ai->name, ai);
871     }
872       
873   ai->exec = g_strdup (exec);
874   
875   if (count)
876     ai->count = atoi (count);
877   else
878     ai->count = 1;
879
880   if (modified)
881     ai->stamp = timestamp_from_iso8601 (modified);
882   else
883     {
884       /* the timestamp attribute has been deprecated but we still parse
885        * it for backward compatibility
886        */
887       if (stamp)
888         ai->stamp = (time_t) atol (stamp);
889       else
890         ai->stamp = time (NULL);
891     }
892 }
893
894 static void
895 parse_mime_type_element (GMarkupParseContext  *context,
896                          ParseData            *parse_data,
897                          const gchar         **attribute_names,
898                          const gchar         **attribute_values,
899                          GError              **error)
900 {
901   const gchar *type;
902   const gchar *attr;
903   gint i;
904   BookmarkItem *item;
905   
906   g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_MIME));
907   
908   i = 0;
909   type = NULL;
910   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
911     {
912       if (IS_ATTRIBUTE (attr, MIME_TYPE_ATTRIBUTE))
913         type = attribute_values[i];
914     }
915
916   if (!type)
917     type = "application/octet-stream";
918
919   g_warn_if_fail (parse_data->current_item != NULL);  
920   item = parse_data->current_item;
921     
922   if (!item->metadata)
923     item->metadata = bookmark_metadata_new ();
924   
925   item->metadata->mime_type = g_strdup (type);
926 }
927
928 static void
929 parse_icon_element (GMarkupParseContext  *context,
930                     ParseData            *parse_data,
931                     const gchar         **attribute_names,
932                     const gchar         **attribute_values,
933                     GError              **error)
934 {
935   const gchar *href;
936   const gchar *type;
937   const gchar *attr;
938   gint i;
939   BookmarkItem *item;
940   
941   g_warn_if_fail ((parse_data != NULL) && (parse_data->state == STATE_ICON));
942   
943   i = 0;
944   href = NULL;
945   type = NULL;
946   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
947     {
948       if (IS_ATTRIBUTE (attr, BOOKMARK_HREF_ATTRIBUTE))
949         href = attribute_values[i];
950       else if (IS_ATTRIBUTE (attr, BOOKMARK_TYPE_ATTRIBUTE))
951         type = attribute_values[i];
952     }
953
954   /* the "href" attribute is mandatory */       
955   if (!href)
956     {
957       g_set_error (error, G_MARKUP_ERROR,
958                    G_MARKUP_ERROR_INVALID_CONTENT,
959                    _("Attribute '%s' of element '%s' not found"),
960                    BOOKMARK_HREF_ATTRIBUTE,
961                    BOOKMARK_ICON_ELEMENT);
962       return;
963     }
964
965   if (!type)
966     type = "application/octet-stream";
967
968   g_warn_if_fail (parse_data->current_item != NULL);  
969   item = parse_data->current_item;
970     
971   if (!item->metadata)
972     item->metadata = bookmark_metadata_new ();
973   
974   item->metadata->icon_href = g_strdup (href);
975   item->metadata->icon_mime = g_strdup (type);
976 }
977
978 /* scans through the attributes of an element for the "xmlns" pragma, and
979  * adds any resulting namespace declaration to a per-parser hashtable, using
980  * the namespace name as a key for the namespace URI; if no key was found,
981  * the namespace is considered as default, and stored under the "default" key.
982  *
983  * FIXME: this works on the assumption that the generator of the XBEL file
984  * is either this code or is smart enough to place the namespace declarations
985  * inside the main root node or inside the metadata node and does not redefine 
986  * a namespace inside an inner node; this does *not* conform to the
987  * XML-NS standard, although is a close approximation.  In order to make this
988  * conformant to the XML-NS specification we should use a per-element
989  * namespace table inside GMarkup and ask it to resolve the namespaces for us.
990  */
991 static void
992 map_namespace_to_name (ParseData    *parse_data,
993                        const gchar **attribute_names,
994                        const gchar **attribute_values)
995 {
996   const gchar *attr;
997   gint i;
998  
999   g_warn_if_fail (parse_data != NULL);
1000   
1001   if (!attribute_names || !attribute_names[0])
1002     return;
1003   
1004   i = 0;
1005   for (attr = attribute_names[i]; attr; attr = attribute_names[++i])
1006     {
1007       if (g_str_has_prefix (attr, "xmlns"))
1008         {
1009           gchar *namespace_name, *namespace_uri;
1010           gchar *p;
1011           
1012           p = g_utf8_strchr (attr, -1, ':');
1013           if (p)
1014             p = g_utf8_next_char (p);
1015           else
1016             p = "default";
1017           
1018           namespace_name = g_strdup (p);
1019           namespace_uri = g_strdup (attribute_values[i]);
1020           
1021           g_hash_table_replace (parse_data->namespaces,
1022                                 namespace_name,
1023                                 namespace_uri);
1024         }
1025      }
1026 }
1027
1028 /* checks whether @element_full is equal to @element.
1029  *
1030  * if @namespace is set, it tries to resolve the namespace to a known URI,
1031  * and if found is prepended to the element name, from which is separated
1032  * using the character specified in the @sep parameter.
1033  */
1034 static gboolean
1035 is_element_full (ParseData   *parse_data,
1036                  const gchar *element_full,
1037                  const gchar *namespace,
1038                  const gchar *element,
1039                  const gchar  sep)
1040 {
1041   gchar *ns_uri, *ns_name;
1042   const gchar *p, *element_name;
1043   gboolean retval;
1044  
1045   g_warn_if_fail (parse_data != NULL);
1046   g_warn_if_fail (element_full != NULL);
1047   
1048   if (!element)
1049     return FALSE;
1050     
1051   /* no namespace requested: dumb element compare */
1052   if (!namespace)
1053     return (0 == strcmp (element_full, element));
1054   
1055   /* search for namespace separator; if none found, assume we are under the
1056    * default namespace, and set ns_name to our "default" marker; if no default
1057    * namespace has been set, just do a plain comparison between @full_element
1058    * and @element.
1059    */
1060   p = g_utf8_strchr (element_full, -1, ':');
1061   if (p)
1062     {
1063       ns_name = g_strndup (element_full, p - element_full);
1064       element_name = g_utf8_next_char (p);
1065     }
1066   else
1067     {
1068       ns_name = g_strdup ("default");
1069       element_name = element_full;
1070     }
1071   
1072   ns_uri = g_hash_table_lookup (parse_data->namespaces, ns_name);  
1073   if (!ns_uri)
1074     {
1075       /* no default namespace found */
1076       g_free (ns_name);
1077       
1078       return (0 == strcmp (element_full, element));
1079     }
1080
1081   retval = (0 == strcmp (ns_uri, namespace) &&
1082             0 == strcmp (element_name, element));
1083   
1084   g_free (ns_name);
1085   
1086   return retval;
1087 }
1088
1089 #define IS_ELEMENT(p,s,e)       (is_element_full ((p), (s), NULL, (e), '\0'))
1090 #define IS_ELEMENT_NS(p,s,n,e)  (is_element_full ((p), (s), (n), (e), '|'))
1091
1092 static void
1093 start_element_raw_cb (GMarkupParseContext *context,
1094                       const gchar         *element_name,
1095                       const gchar        **attribute_names,
1096                       const gchar        **attribute_values,
1097                       gpointer             user_data,
1098                       GError             **error)
1099 {
1100   ParseData *parse_data = (ParseData *) user_data;
1101
1102   /* we must check for namespace declarations first
1103    * 
1104    * XXX - we could speed up things by checking for namespace declarations
1105    * only on the root node, where they usually are; this would probably break
1106    * on streams not produced by us or by "smart" generators
1107    */
1108   map_namespace_to_name (parse_data, attribute_names, attribute_values);
1109   
1110   switch (parse_data->state)
1111     {
1112     case STATE_STARTED:
1113       if (IS_ELEMENT (parse_data, element_name, XBEL_ROOT_ELEMENT))
1114         {
1115           const gchar *attr;
1116           gint i;
1117           
1118           i = 0;
1119           for (attr = attribute_names[i]; attr; attr = attribute_names[++i])
1120             {
1121               if ((IS_ATTRIBUTE (attr, XBEL_VERSION_ATTRIBUTE)) &&
1122                   (0 == strcmp (attribute_values[i], XBEL_VERSION)))
1123                 parse_data->state = STATE_ROOT;
1124             }
1125         }
1126       else
1127         g_set_error (error, G_MARKUP_ERROR,
1128                      G_MARKUP_ERROR_INVALID_CONTENT,
1129                      _("Unexpected tag '%s', tag '%s' expected"),
1130                      element_name, XBEL_ROOT_ELEMENT);
1131       break;
1132     case STATE_ROOT:
1133       if (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT))
1134         parse_data->state = STATE_TITLE;
1135       else if (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT))
1136         parse_data->state = STATE_DESC;
1137       else if (IS_ELEMENT (parse_data, element_name, XBEL_BOOKMARK_ELEMENT))
1138         {
1139           GError *inner_error = NULL;
1140           
1141           parse_data->state = STATE_BOOKMARK;
1142           
1143           parse_bookmark_element (context,
1144                                   parse_data,
1145                                   attribute_names,
1146                                   attribute_values,
1147                                   &inner_error);
1148           if (inner_error)
1149             g_propagate_error (error, inner_error);
1150         }
1151       else
1152         g_set_error (error, G_MARKUP_ERROR,
1153                      G_MARKUP_ERROR_INVALID_CONTENT,
1154                      _("Unexpected tag '%s' inside '%s'"),
1155                      element_name,
1156                      XBEL_ROOT_ELEMENT);
1157       break;
1158     case STATE_BOOKMARK:
1159       if (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT))
1160         parse_data->state = STATE_TITLE;
1161       else if (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT))
1162         parse_data->state = STATE_DESC;
1163       else if (IS_ELEMENT (parse_data, element_name, XBEL_INFO_ELEMENT))
1164         parse_data->state = STATE_INFO;
1165       else
1166         g_set_error (error, G_MARKUP_ERROR,
1167                      G_MARKUP_ERROR_INVALID_CONTENT,
1168                      _("Unexpected tag '%s' inside '%s'"),
1169                      element_name,
1170                      XBEL_BOOKMARK_ELEMENT);
1171       break;
1172     case STATE_INFO:
1173       if (IS_ELEMENT (parse_data, element_name, XBEL_METADATA_ELEMENT))
1174         {
1175           const gchar *attr;
1176           gint i;
1177           
1178           i = 0;
1179           for (attr = attribute_names[i]; attr; attr = attribute_names[++i])
1180             {
1181               if ((IS_ATTRIBUTE (attr, XBEL_OWNER_ATTRIBUTE)) &&
1182                   (0 == strcmp (attribute_values[i], BOOKMARK_METADATA_OWNER)))
1183                 {
1184                   parse_data->state = STATE_METADATA;
1185                   
1186                   if (!parse_data->current_item->metadata)
1187                     parse_data->current_item->metadata = bookmark_metadata_new ();
1188                 }
1189             }
1190         }
1191       else
1192         g_set_error (error, G_MARKUP_ERROR,
1193                      G_MARKUP_ERROR_INVALID_CONTENT,
1194                      _("Unexpected tag '%s', tag '%s' expected"),
1195                      element_name,
1196                      XBEL_METADATA_ELEMENT);
1197       break;
1198     case STATE_METADATA:
1199       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATIONS_ELEMENT))
1200         parse_data->state = STATE_APPLICATIONS;
1201       else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUPS_ELEMENT))
1202         parse_data->state = STATE_GROUPS;
1203       else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_PRIVATE_ELEMENT))
1204         parse_data->current_item->metadata->is_private = TRUE;
1205       else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT))
1206         {
1207           GError *inner_error = NULL;
1208           
1209           parse_data->state = STATE_ICON;
1210           
1211           parse_icon_element (context,
1212                               parse_data,
1213                               attribute_names,
1214                               attribute_values,
1215                               &inner_error);
1216           if (inner_error)
1217             g_propagate_error (error, inner_error);
1218         }
1219       else if (IS_ELEMENT_NS (parse_data, element_name, MIME_NAMESPACE_URI, MIME_TYPE_ELEMENT))
1220         {
1221           GError *inner_error = NULL;
1222           
1223           parse_data->state = STATE_MIME;
1224           
1225           parse_mime_type_element (context,
1226                                    parse_data,
1227                                    attribute_names,
1228                                    attribute_values,
1229                                    &inner_error);
1230           if (inner_error)
1231             g_propagate_error (error, inner_error);
1232         }
1233       else
1234         g_set_error (error, G_MARKUP_ERROR,
1235                      G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1236                      _("Unexpected tag '%s' inside '%s'"),
1237                      element_name,
1238                      XBEL_METADATA_ELEMENT);
1239       break;
1240     case STATE_APPLICATIONS:
1241       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATION_ELEMENT))
1242         {
1243           GError *inner_error = NULL;
1244           
1245           parse_data->state = STATE_APPLICATION;
1246           
1247           parse_application_element (context,
1248                                      parse_data,
1249                                      attribute_names,
1250                                      attribute_values,
1251                                      &inner_error);
1252           if (inner_error)
1253             g_propagate_error (error, inner_error);
1254         }
1255       else
1256         g_set_error (error, G_MARKUP_ERROR,
1257                      G_MARKUP_ERROR_INVALID_CONTENT,
1258                      _("Unexpected tag '%s', tag '%s' expected"),
1259                      element_name,
1260                      BOOKMARK_APPLICATION_ELEMENT);
1261       break;
1262     case STATE_GROUPS:
1263       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUP_ELEMENT))
1264         parse_data->state = STATE_GROUP;
1265       else
1266         g_set_error (error, G_MARKUP_ERROR,
1267                      G_MARKUP_ERROR_INVALID_CONTENT,
1268                      _("Unexpected tag '%s', tag '%s' expected"),
1269                      element_name,
1270                      BOOKMARK_GROUP_ELEMENT);
1271       break;
1272     case STATE_ICON:
1273       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT))
1274         {
1275           GError *inner_error = NULL;
1276           
1277           parse_icon_element (context,
1278                               parse_data,
1279                               attribute_names,
1280                               attribute_values,
1281                               &inner_error);
1282           if (inner_error)
1283             g_propagate_error (error, inner_error);
1284         }
1285       else
1286         g_set_error (error, G_MARKUP_ERROR,
1287                      G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1288                      _("Unexpected tag '%s' inside '%s'"),
1289                      element_name,
1290                      XBEL_METADATA_ELEMENT);
1291       break;
1292     default:
1293       g_warn_if_reached ();
1294       break;
1295     }
1296 }
1297
1298 static void
1299 end_element_raw_cb (GMarkupParseContext *context,
1300                     const gchar         *element_name,
1301                     gpointer             user_data,
1302                     GError             **error)
1303 {
1304   ParseData *parse_data = (ParseData *) user_data;
1305   
1306   if (IS_ELEMENT (parse_data, element_name, XBEL_ROOT_ELEMENT))
1307     parse_data->state = STATE_FINISHED;
1308   else if (IS_ELEMENT (parse_data, element_name, XBEL_BOOKMARK_ELEMENT))
1309     {
1310       parse_data->current_item = NULL;
1311       
1312       parse_data->state = STATE_ROOT;
1313     }
1314   else if ((IS_ELEMENT (parse_data, element_name, XBEL_INFO_ELEMENT)) ||
1315            (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT)) ||
1316            (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT)))
1317     {
1318       if (parse_data->current_item)
1319         parse_data->state = STATE_BOOKMARK;
1320       else
1321         parse_data->state = STATE_ROOT;
1322     }
1323   else if (IS_ELEMENT (parse_data, element_name, XBEL_METADATA_ELEMENT))
1324     parse_data->state = STATE_INFO;
1325   else if (IS_ELEMENT_NS (parse_data, element_name,
1326                           BOOKMARK_NAMESPACE_URI,
1327                           BOOKMARK_APPLICATION_ELEMENT))
1328     parse_data->state = STATE_APPLICATIONS;
1329   else if (IS_ELEMENT_NS (parse_data, element_name,
1330                           BOOKMARK_NAMESPACE_URI,
1331                           BOOKMARK_GROUP_ELEMENT))
1332     parse_data->state = STATE_GROUPS;
1333   else if ((IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATIONS_ELEMENT)) ||
1334            (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUPS_ELEMENT)) ||
1335            (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_PRIVATE_ELEMENT)) ||
1336            (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT)) ||
1337            (IS_ELEMENT_NS (parse_data, element_name, MIME_NAMESPACE_URI, MIME_TYPE_ELEMENT)))
1338     parse_data->state = STATE_METADATA;
1339 }
1340
1341 static void
1342 text_raw_cb (GMarkupParseContext *context,
1343              const gchar         *text,
1344              gsize                length,
1345              gpointer             user_data,
1346              GError             **error)
1347 {
1348   ParseData *parse_data = (ParseData *) user_data;
1349   gchar *payload;
1350   
1351   payload = g_strndup (text, length);
1352   
1353   switch (parse_data->state)
1354     {
1355     case STATE_TITLE:
1356       if (parse_data->current_item)
1357         {
1358           g_free (parse_data->current_item->title);
1359           parse_data->current_item->title = g_strdup (payload);
1360         }
1361       else
1362         {
1363           g_free (parse_data->bookmark_file->title);
1364           parse_data->bookmark_file->title = g_strdup (payload);
1365         }
1366       break;
1367     case STATE_DESC:
1368       if (parse_data->current_item)
1369         {
1370           g_free (parse_data->current_item->description);
1371           parse_data->current_item->description = g_strdup (payload);
1372         }
1373       else
1374         {
1375           g_free (parse_data->bookmark_file->description);
1376           parse_data->bookmark_file->description = g_strdup (payload);
1377         }
1378       break;
1379     case STATE_GROUP:
1380       {
1381       GList *groups;
1382       
1383       g_warn_if_fail (parse_data->current_item != NULL);
1384       
1385       if (!parse_data->current_item->metadata)
1386         parse_data->current_item->metadata = bookmark_metadata_new ();
1387       
1388       groups = parse_data->current_item->metadata->groups;
1389       parse_data->current_item->metadata->groups = g_list_prepend (groups, g_strdup (payload));
1390       }
1391       break;
1392     case STATE_ROOT:
1393     case STATE_BOOKMARK:
1394     case STATE_INFO:
1395     case STATE_METADATA:
1396     case STATE_APPLICATIONS:
1397     case STATE_APPLICATION:
1398     case STATE_GROUPS:
1399     case STATE_MIME:
1400     case STATE_ICON:
1401       break;
1402     default:
1403       g_warn_if_reached ();
1404       break;
1405     }
1406   
1407   g_free (payload);
1408 }
1409
1410 static const GMarkupParser markup_parser =
1411 {
1412   start_element_raw_cb, /* start_element */
1413   end_element_raw_cb,   /* end_element */
1414   text_raw_cb,          /* text */
1415   NULL,                 /* passthrough */
1416   NULL
1417 };
1418
1419 static gboolean
1420 g_bookmark_file_parse (GBookmarkFile  *bookmark,
1421                          const gchar  *buffer,
1422                          gsize         length,
1423                          GError       **error)
1424 {
1425   GMarkupParseContext *context;
1426   ParseData *parse_data;
1427   GError *parse_error, *end_error;
1428   gboolean retval;
1429   
1430   g_warn_if_fail (bookmark != NULL);
1431
1432   if (!buffer)
1433     return FALSE;
1434
1435   parse_error = NULL;
1436   end_error = NULL;
1437   
1438   if (length == (gsize) -1)
1439     length = strlen (buffer);
1440
1441   parse_data = parse_data_new ();
1442   parse_data->bookmark_file = bookmark;
1443   
1444   context = g_markup_parse_context_new (&markup_parser,
1445                                         0,
1446                                         parse_data,
1447                                         (GDestroyNotify) parse_data_free);
1448   
1449   retval = g_markup_parse_context_parse (context,
1450                                          buffer,
1451                                          length,
1452                                          &parse_error);
1453   if (!retval)
1454     g_propagate_error (error, parse_error);
1455   else
1456    {
1457      retval = g_markup_parse_context_end_parse (context, &end_error);
1458       if (!retval)
1459         g_propagate_error (error, end_error);
1460    }
1461  
1462   g_markup_parse_context_free (context);
1463
1464   return retval;
1465 }
1466
1467 static gchar *
1468 g_bookmark_file_dump (GBookmarkFile  *bookmark,
1469                       gsize          *length,
1470                       GError        **error)
1471 {
1472   GString *retval;
1473   gchar *buffer;
1474   GList *l;
1475   
1476   retval = g_string_sized_new (4096);
1477
1478   g_string_append (retval,
1479                    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1480 #if 0
1481                    /* XXX - do we really need the doctype? */
1482                    "<!DOCTYPE " XBEL_DTD_NICK "\n"
1483                    "  PUBLIC \"" XBEL_DTD_SYSTEM "\"\n"
1484                    "         \"" XBEL_DTD_URI "\">\n"
1485 #endif
1486                    "<" XBEL_ROOT_ELEMENT " " XBEL_VERSION_ATTRIBUTE "=\"" XBEL_VERSION "\"\n"
1487                    "      xmlns:" BOOKMARK_NAMESPACE_NAME "=\"" BOOKMARK_NAMESPACE_URI "\"\n"
1488                    "      xmlns:" MIME_NAMESPACE_NAME     "=\"" MIME_NAMESPACE_URI "\"\n>");
1489   
1490   if (bookmark->title)
1491     {
1492       gchar *escaped_title;
1493  
1494       escaped_title = g_markup_escape_text (bookmark->title, -1);
1495
1496       buffer = g_strconcat ("  "
1497                             "<" XBEL_TITLE_ELEMENT ">",
1498                             escaped_title,
1499                             "</" XBEL_TITLE_ELEMENT ">\n", NULL);
1500       
1501       g_string_append (retval, buffer);
1502
1503       g_free (buffer);
1504       g_free (escaped_title);
1505     }
1506   
1507   if (bookmark->description)
1508     {
1509       gchar *escaped_desc;
1510  
1511       escaped_desc = g_markup_escape_text (bookmark->description, -1);
1512
1513       buffer = g_strconcat ("  "
1514                             "<" XBEL_DESC_ELEMENT ">",
1515                             escaped_desc,
1516                             "</" XBEL_DESC_ELEMENT ">\n", NULL);
1517       g_string_append (retval, buffer);
1518
1519       g_free (buffer);
1520       g_free (escaped_desc);
1521     }
1522   
1523   if (!bookmark->items)
1524     goto out;
1525   else
1526     retval = g_string_append (retval, "\n");
1527
1528   /* the items are stored in reverse order */
1529   for (l = g_list_last (bookmark->items);
1530        l != NULL;
1531        l = l->prev)
1532     {
1533       BookmarkItem *item = (BookmarkItem *) l->data;
1534       gchar *item_dump;
1535       
1536       item_dump = bookmark_item_dump (item);
1537       if (!item_dump)
1538         continue;
1539       
1540       retval = g_string_append (retval, item_dump);
1541       
1542       g_free (item_dump);
1543     }
1544
1545 out:
1546   g_string_append (retval, "</" XBEL_ROOT_ELEMENT ">");
1547   
1548   if (length)
1549     *length = retval->len;
1550   
1551   return g_string_free (retval, FALSE);
1552 }
1553
1554 /**************
1555  *    Misc    *
1556  **************/
1557  
1558 /* converts a Unix timestamp in a ISO 8601 compliant string; you
1559  * should free the returned string.
1560  */
1561 static gchar *
1562 timestamp_to_iso8601 (time_t timestamp)
1563 {
1564   GTimeVal stamp;
1565
1566   if (timestamp == (time_t) -1)
1567     g_get_current_time (&stamp);
1568   else
1569     {
1570       stamp.tv_sec = timestamp;
1571       stamp.tv_usec = 0;
1572     }
1573
1574   return g_time_val_to_iso8601 (&stamp);
1575 }
1576
1577 static time_t
1578 timestamp_from_iso8601 (const gchar *iso_date)
1579 {
1580   GTimeVal stamp;
1581
1582   if (!g_time_val_from_iso8601 (iso_date, &stamp))
1583     return (time_t) -1;
1584
1585   return (time_t) stamp.tv_sec;
1586 }
1587
1588 G_DEFINE_QUARK (g-bookmark-file-error-quark, g_bookmark_file_error)
1589
1590 /********************
1591  *    Public API    *
1592  ********************/
1593
1594 /**
1595  * g_bookmark_file_new:
1596  *
1597  * Creates a new empty #GBookmarkFile object.
1598  *
1599  * Use g_bookmark_file_load_from_file(), g_bookmark_file_load_from_data()
1600  * or g_bookmark_file_load_from_data_dirs() to read an existing bookmark
1601  * file.
1602  *
1603  * Return value: an empty #GBookmarkFile
1604  *
1605  * Since: 2.12
1606  */
1607 GBookmarkFile *
1608 g_bookmark_file_new (void)
1609 {
1610   GBookmarkFile *bookmark;
1611   
1612   bookmark = g_new (GBookmarkFile, 1);
1613   
1614   g_bookmark_file_init (bookmark);
1615   
1616   return bookmark;
1617 }
1618
1619 /**
1620  * g_bookmark_file_free:
1621  * @bookmark: a #GBookmarkFile
1622  *
1623  * Frees a #GBookmarkFile.
1624  *
1625  * Since: 2.12
1626  */
1627 void
1628 g_bookmark_file_free (GBookmarkFile *bookmark)
1629 {
1630   if (!bookmark)
1631     return;
1632   
1633   g_bookmark_file_clear (bookmark);
1634   
1635   g_free (bookmark);  
1636 }
1637
1638 /**
1639  * g_bookmark_file_load_from_data:
1640  * @bookmark: an empty #GBookmarkFile struct
1641  * @data: desktop bookmarks loaded in memory
1642  * @length: the length of @data in bytes
1643  * @error: return location for a #GError, or %NULL
1644  *
1645  * Loads a bookmark file from memory into an empty #GBookmarkFile
1646  * structure.  If the object cannot be created then @error is set to a
1647  * #GBookmarkFileError.
1648  *
1649  * Return value: %TRUE if a desktop bookmark could be loaded.
1650  *
1651  * Since: 2.12
1652  */
1653 gboolean
1654 g_bookmark_file_load_from_data (GBookmarkFile  *bookmark,
1655                                 const gchar    *data,
1656                                 gsize           length,
1657                                 GError        **error)
1658 {
1659   GError *parse_error;
1660   gboolean retval;
1661   
1662   g_return_val_if_fail (bookmark != NULL, FALSE);
1663
1664   if (length == (gsize) -1)
1665     length = strlen (data);
1666
1667   if (bookmark->items)
1668     {
1669       g_bookmark_file_clear (bookmark);
1670       g_bookmark_file_init (bookmark);
1671     }
1672
1673   parse_error = NULL;
1674   retval = g_bookmark_file_parse (bookmark, data, length, &parse_error);
1675
1676   if (!retval)
1677     g_propagate_error (error, parse_error);
1678
1679   return retval;
1680 }
1681
1682 /**
1683  * g_bookmark_file_load_from_file:
1684  * @bookmark: an empty #GBookmarkFile struct
1685  * @filename: the path of a filename to load, in the GLib file name encoding
1686  * @error: return location for a #GError, or %NULL
1687  *
1688  * Loads a desktop bookmark file into an empty #GBookmarkFile structure.
1689  * If the file could not be loaded then @error is set to either a #GFileError
1690  * or #GBookmarkFileError.
1691  *
1692  * Return value: %TRUE if a desktop bookmark file could be loaded
1693  *
1694  * Since: 2.12
1695  */
1696 gboolean
1697 g_bookmark_file_load_from_file (GBookmarkFile  *bookmark,
1698                                 const gchar    *filename,
1699                                 GError        **error)
1700 {
1701   gchar *buffer;
1702   gsize len;
1703   GError *read_error;
1704   gboolean retval;
1705         
1706   g_return_val_if_fail (bookmark != NULL, FALSE);
1707   g_return_val_if_fail (filename != NULL, FALSE);
1708
1709   read_error = NULL;
1710   g_file_get_contents (filename, &buffer, &len, &read_error);
1711   if (read_error)
1712     {
1713       g_propagate_error (error, read_error);
1714
1715       return FALSE;
1716     }
1717   
1718   read_error = NULL;
1719   retval = g_bookmark_file_load_from_data (bookmark,
1720                                            buffer,
1721                                            len,
1722                                            &read_error);
1723   if (read_error)
1724     {
1725       g_propagate_error (error, read_error);
1726
1727       g_free (buffer);
1728
1729       return FALSE;
1730     }
1731
1732   g_free (buffer);
1733
1734   return retval;
1735 }
1736
1737
1738 /* Iterates through all the directories in *dirs trying to
1739  * find file.  When it successfully locates file, returns a
1740  * string its absolute path.  It also leaves the unchecked
1741  * directories in *dirs.  You should free the returned string
1742  *
1743  * Adapted from gkeyfile.c
1744  */
1745 static gchar *
1746 find_file_in_data_dirs (const gchar   *file,
1747                         gchar       ***dirs,
1748                         GError       **error)
1749 {
1750   gchar **data_dirs, *data_dir, *path;
1751
1752   path = NULL;
1753
1754   if (dirs == NULL)
1755     return NULL;
1756
1757   data_dirs = *dirs;
1758   path = NULL;
1759   while (data_dirs && (data_dir = *data_dirs) && !path)
1760     {
1761       gchar *candidate_file, *sub_dir;
1762
1763       candidate_file = (gchar *) file;
1764       sub_dir = g_strdup ("");
1765       while (candidate_file != NULL && !path)
1766         {
1767           gchar *p;
1768
1769           path = g_build_filename (data_dir, sub_dir,
1770                                    candidate_file, NULL);
1771
1772           candidate_file = strchr (candidate_file, '-');
1773
1774           if (candidate_file == NULL)
1775             break;
1776
1777           candidate_file++;
1778
1779           g_free (sub_dir);
1780           sub_dir = g_strndup (file, candidate_file - file - 1);
1781
1782           for (p = sub_dir; *p != '\0'; p++)
1783             {
1784               if (*p == '-')
1785                 *p = G_DIR_SEPARATOR;
1786             }
1787         }
1788       g_free (sub_dir);
1789       data_dirs++;
1790     }
1791
1792   *dirs = data_dirs;
1793
1794   if (!path)
1795     {
1796       g_set_error_literal (error, G_BOOKMARK_FILE_ERROR,
1797                            G_BOOKMARK_FILE_ERROR_FILE_NOT_FOUND,
1798                            _("No valid bookmark file found in data dirs"));
1799       
1800       return NULL;
1801     }
1802   
1803   return path;
1804 }
1805
1806
1807 /**
1808  * g_bookmark_file_load_from_data_dirs:
1809  * @bookmark: a #GBookmarkFile
1810  * @file: a relative path to a filename to open and parse
1811  * @full_path: (allow-none): return location for a string containing the full path
1812  *   of the file, or %NULL
1813  * @error: return location for a #GError, or %NULL
1814  *
1815  * This function looks for a desktop bookmark file named @file in the
1816  * paths returned from g_get_user_data_dir() and g_get_system_data_dirs(), 
1817  * loads the file into @bookmark and returns the file's full path in 
1818  * @full_path.  If the file could not be loaded then an %error is
1819  * set to either a #GFileError or #GBookmarkFileError.
1820  *
1821  * Return value: %TRUE if a key file could be loaded, %FALSE otherwise
1822  *
1823  * Since: 2.12
1824  */
1825 gboolean
1826 g_bookmark_file_load_from_data_dirs (GBookmarkFile  *bookmark,
1827                                      const gchar    *file,
1828                                      gchar         **full_path,
1829                                      GError        **error)
1830 {
1831   GError *file_error = NULL;
1832   gchar **all_data_dirs, **data_dirs;
1833   const gchar *user_data_dir;
1834   const gchar * const * system_data_dirs;
1835   gsize i, j;
1836   gchar *output_path;
1837   gboolean found_file;
1838   
1839   g_return_val_if_fail (bookmark != NULL, FALSE);
1840   g_return_val_if_fail (!g_path_is_absolute (file), FALSE);
1841   
1842   user_data_dir = g_get_user_data_dir ();
1843   system_data_dirs = g_get_system_data_dirs ();
1844   all_data_dirs = g_new0 (gchar *, g_strv_length ((gchar **)system_data_dirs) + 2);
1845
1846   i = 0;
1847   all_data_dirs[i++] = g_strdup (user_data_dir);
1848
1849   j = 0;
1850   while (system_data_dirs[j] != NULL)
1851     all_data_dirs[i++] = g_strdup (system_data_dirs[j++]);
1852
1853   found_file = FALSE;
1854   data_dirs = all_data_dirs;
1855   output_path = NULL;
1856   while (*data_dirs != NULL && !found_file)
1857     {
1858       g_free (output_path);
1859
1860       output_path = find_file_in_data_dirs (file, &data_dirs, &file_error);
1861       
1862       if (file_error)
1863         {
1864           g_propagate_error (error, file_error);
1865           break;
1866         }
1867
1868       found_file = g_bookmark_file_load_from_file (bookmark,
1869                                                    output_path,
1870                                                    &file_error);
1871       if (file_error)
1872         {
1873           g_propagate_error (error, file_error);
1874           break;
1875         }
1876     }
1877
1878   if (found_file && full_path)
1879     *full_path = output_path;
1880   else 
1881     g_free (output_path);
1882
1883   g_strfreev (all_data_dirs);
1884
1885   return found_file;
1886 }
1887
1888
1889 /**
1890  * g_bookmark_file_to_data:
1891  * @bookmark: a #GBookmarkFile
1892  * @length: (allow-none) (out): return location for the length of the returned string, or %NULL
1893  * @error: return location for a #GError, or %NULL
1894  *
1895  * This function outputs @bookmark as a string.
1896  *
1897  * Return value: a newly allocated string holding
1898  *   the contents of the #GBookmarkFile
1899  *
1900  * Since: 2.12
1901  */
1902 gchar *
1903 g_bookmark_file_to_data (GBookmarkFile  *bookmark,
1904                          gsize          *length,
1905                          GError        **error)
1906 {
1907   GError *write_error = NULL;
1908   gchar *retval;
1909   
1910   g_return_val_if_fail (bookmark != NULL, NULL);
1911   
1912   retval = g_bookmark_file_dump (bookmark, length, &write_error);
1913   if (write_error)
1914     {
1915       g_propagate_error (error, write_error);
1916       
1917       return NULL;
1918     }
1919       
1920   return retval;
1921 }
1922
1923 /**
1924  * g_bookmark_file_to_file:
1925  * @bookmark: a #GBookmarkFile
1926  * @filename: path of the output file
1927  * @error: return location for a #GError, or %NULL
1928  *
1929  * This function outputs @bookmark into a file.  The write process is
1930  * guaranteed to be atomic by using g_file_set_contents() internally.
1931  *
1932  * Return value: %TRUE if the file was successfully written.
1933  *
1934  * Since: 2.12
1935  */
1936 gboolean
1937 g_bookmark_file_to_file (GBookmarkFile  *bookmark,
1938                          const gchar    *filename,
1939                          GError        **error)
1940 {
1941   gchar *data;
1942   GError *data_error, *write_error;
1943   gsize len;
1944   gboolean retval;
1945
1946   g_return_val_if_fail (bookmark != NULL, FALSE);
1947   g_return_val_if_fail (filename != NULL, FALSE);
1948   
1949   data_error = NULL;
1950   data = g_bookmark_file_to_data (bookmark, &len, &data_error);
1951   if (data_error)
1952     {
1953       g_propagate_error (error, data_error);
1954       
1955       return FALSE;
1956     }
1957
1958   write_error = NULL;
1959   g_file_set_contents (filename, data, len, &write_error);
1960   if (write_error)
1961     {
1962       g_propagate_error (error, write_error);
1963       
1964       retval = FALSE;
1965     }
1966   else
1967     retval = TRUE;
1968
1969   g_free (data);
1970   
1971   return retval;
1972 }
1973
1974 static BookmarkItem *
1975 g_bookmark_file_lookup_item (GBookmarkFile *bookmark,
1976                              const gchar   *uri)
1977 {
1978   g_warn_if_fail (bookmark != NULL && uri != NULL);
1979   
1980   return g_hash_table_lookup (bookmark->items_by_uri, uri);
1981 }
1982
1983 /* this function adds a new item to the list */
1984 static void
1985 g_bookmark_file_add_item (GBookmarkFile  *bookmark,
1986                           BookmarkItem   *item,
1987                           GError        **error)
1988 {
1989   g_warn_if_fail (bookmark != NULL);
1990   g_warn_if_fail (item != NULL);
1991
1992   /* this should never happen; and if it does, then we are
1993    * screwing up something big time.
1994    */
1995   if (G_UNLIKELY (g_bookmark_file_has_item (bookmark, item->uri)))
1996     {
1997       g_set_error (error, G_BOOKMARK_FILE_ERROR,
1998                    G_BOOKMARK_FILE_ERROR_INVALID_URI,
1999                    _("A bookmark for URI '%s' already exists"),
2000                    item->uri);
2001       return;
2002     }
2003   
2004   bookmark->items = g_list_prepend (bookmark->items, item);
2005   
2006   g_hash_table_replace (bookmark->items_by_uri,
2007                         item->uri,
2008                         item);
2009
2010   if (item->added == (time_t) -1)
2011     item->added = time (NULL);
2012   
2013   if (item->modified == (time_t) -1)
2014     item->modified = time (NULL);
2015 }
2016
2017 /**
2018  * g_bookmark_file_remove_item:
2019  * @bookmark: a #GBookmarkFile
2020  * @uri: a valid URI
2021  * @error: return location for a #GError, or %NULL
2022  *
2023  * Removes the bookmark for @uri from the bookmark file @bookmark.
2024  *
2025  * Return value: %TRUE if the bookmark was removed successfully.
2026  * 
2027  * Since: 2.12
2028  */
2029 gboolean
2030 g_bookmark_file_remove_item (GBookmarkFile  *bookmark,
2031                              const gchar    *uri,
2032                              GError        **error)
2033 {
2034   BookmarkItem *item;
2035   
2036   g_return_val_if_fail (bookmark != NULL, FALSE);
2037   g_return_val_if_fail (uri != NULL, FALSE);
2038   
2039   item = g_bookmark_file_lookup_item (bookmark, uri);
2040   
2041   if (!item)
2042     {
2043       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2044                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2045                    _("No bookmark found for URI '%s'"),
2046                    uri);
2047       return FALSE;
2048     }
2049
2050   bookmark->items = g_list_remove (bookmark->items, item);
2051   g_hash_table_remove (bookmark->items_by_uri, item->uri);  
2052   
2053   bookmark_item_free (item);
2054
2055   return TRUE;
2056 }
2057
2058 /**
2059  * g_bookmark_file_has_item:
2060  * @bookmark: a #GBookmarkFile
2061  * @uri: a valid URI
2062  *
2063  * Looks whether the desktop bookmark has an item with its URI set to @uri.
2064  *
2065  * Return value: %TRUE if @uri is inside @bookmark, %FALSE otherwise
2066  *
2067  * Since: 2.12
2068  */
2069 gboolean
2070 g_bookmark_file_has_item (GBookmarkFile *bookmark,
2071                           const gchar   *uri)
2072 {
2073   g_return_val_if_fail (bookmark != NULL, FALSE);
2074   g_return_val_if_fail (uri != NULL, FALSE);
2075   
2076   return (NULL != g_hash_table_lookup (bookmark->items_by_uri, uri));
2077 }
2078
2079 /**
2080  * g_bookmark_file_get_uris:
2081  * @bookmark: a #GBookmarkFile
2082  * @length: (allow-none) (out): return location for the number of returned URIs, or %NULL
2083  *
2084  * Returns all URIs of the bookmarks in the bookmark file @bookmark.
2085  * The array of returned URIs will be %NULL-terminated, so @length may
2086  * optionally be %NULL.
2087  *
2088  * Return value: (array length=length) (transfer full): a newly allocated %NULL-terminated array of strings.
2089  *   Use g_strfreev() to free it.
2090  *
2091  * Since: 2.12
2092  */
2093 gchar **
2094 g_bookmark_file_get_uris (GBookmarkFile *bookmark,
2095                           gsize         *length)
2096 {
2097   GList *l;
2098   gchar **uris;
2099   gsize i, n_items;
2100   
2101   g_return_val_if_fail (bookmark != NULL, NULL);
2102   
2103   n_items = g_list_length (bookmark->items); 
2104   uris = g_new0 (gchar *, n_items + 1);
2105
2106   /* the items are stored in reverse order, so we walk the list backward */
2107   for (l = g_list_last (bookmark->items), i = 0; l != NULL; l = l->prev)
2108     {
2109       BookmarkItem *item = (BookmarkItem *) l->data;
2110       
2111       g_warn_if_fail (item != NULL);
2112       
2113       uris[i++] = g_strdup (item->uri);
2114     }
2115   uris[i] = NULL;
2116   
2117   if (length)
2118     *length = i;
2119   
2120   return uris;
2121 }
2122
2123 /**
2124  * g_bookmark_file_set_title:
2125  * @bookmark: a #GBookmarkFile
2126  * @uri: (allow-none): a valid URI or %NULL
2127  * @title: a UTF-8 encoded string
2128  *
2129  * Sets @title as the title of the bookmark for @uri inside the
2130  * bookmark file @bookmark.
2131  *
2132  * If @uri is %NULL, the title of @bookmark is set.
2133  *
2134  * If a bookmark for @uri cannot be found then it is created.
2135  *
2136  * Since: 2.12
2137  */
2138 void
2139 g_bookmark_file_set_title (GBookmarkFile *bookmark,
2140                            const gchar   *uri,
2141                            const gchar   *title)
2142 {
2143   g_return_if_fail (bookmark != NULL);
2144   
2145   if (!uri)
2146     {
2147       g_free (bookmark->title);
2148       bookmark->title = g_strdup (title);
2149     }
2150   else
2151     {
2152       BookmarkItem *item;
2153       
2154       item = g_bookmark_file_lookup_item (bookmark, uri);
2155       if (!item)
2156         {
2157           item = bookmark_item_new (uri);
2158           g_bookmark_file_add_item (bookmark, item, NULL);
2159         }
2160       
2161       g_free (item->title);
2162       item->title = g_strdup (title);
2163       
2164       item->modified = time (NULL);
2165     }
2166 }
2167
2168 /**
2169  * g_bookmark_file_get_title:
2170  * @bookmark: a #GBookmarkFile
2171  * @uri: (allow-none): a valid URI or %NULL
2172  * @error: return location for a #GError, or %NULL
2173  *
2174  * Returns the title of the bookmark for @uri.
2175  *
2176  * If @uri is %NULL, the title of @bookmark is returned.
2177  *
2178  * In the event the URI cannot be found, %NULL is returned and
2179  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2180  *
2181  * Return value: a newly allocated string or %NULL if the specified
2182  *   URI cannot be found.
2183  *
2184  * Since: 2.12
2185  */
2186 gchar *
2187 g_bookmark_file_get_title (GBookmarkFile  *bookmark,
2188                            const gchar    *uri,
2189                            GError        **error)
2190 {
2191   BookmarkItem *item;
2192   
2193   g_return_val_if_fail (bookmark != NULL, NULL);
2194   
2195   if (!uri)
2196     return g_strdup (bookmark->title);
2197   
2198   item = g_bookmark_file_lookup_item (bookmark, uri);
2199   if (!item)
2200     {
2201       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2202                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2203                    _("No bookmark found for URI '%s'"),
2204                    uri);
2205       return NULL;
2206     }
2207   
2208   return g_strdup (item->title);
2209 }
2210
2211 /**
2212  * g_bookmark_file_set_description:
2213  * @bookmark: a #GBookmarkFile
2214  * @uri: (allow-none): a valid URI or %NULL
2215  * @description: a string
2216  *
2217  * Sets @description as the description of the bookmark for @uri.
2218  *
2219  * If @uri is %NULL, the description of @bookmark is set.
2220  *
2221  * If a bookmark for @uri cannot be found then it is created.
2222  *
2223  * Since: 2.12
2224  */
2225 void
2226 g_bookmark_file_set_description (GBookmarkFile *bookmark,
2227                                  const gchar   *uri,
2228                                  const gchar   *description)
2229 {
2230   g_return_if_fail (bookmark != NULL);
2231
2232   if (!uri)
2233     {
2234       g_free (bookmark->description); 
2235       bookmark->description = g_strdup (description);
2236     }
2237   else
2238     {
2239       BookmarkItem *item;
2240       
2241       item = g_bookmark_file_lookup_item (bookmark, uri);
2242       if (!item)
2243         {
2244           item = bookmark_item_new (uri);
2245           g_bookmark_file_add_item (bookmark, item, NULL);
2246         }
2247       
2248       g_free (item->description);
2249       item->description = g_strdup (description);
2250       
2251       item->modified = time (NULL);
2252     }
2253 }
2254
2255 /**
2256  * g_bookmark_file_get_description:
2257  * @bookmark: a #GBookmarkFile
2258  * @uri: a valid URI
2259  * @error: return location for a #GError, or %NULL
2260  *
2261  * Retrieves the description of the bookmark for @uri.
2262  *
2263  * In the event the URI cannot be found, %NULL is returned and
2264  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2265  *
2266  * Return value: a newly allocated string or %NULL if the specified
2267  *   URI cannot be found.
2268  *
2269  * Since: 2.12
2270  */
2271 gchar *
2272 g_bookmark_file_get_description (GBookmarkFile  *bookmark,
2273                                  const gchar    *uri,
2274                                  GError        **error)
2275 {
2276   BookmarkItem *item;
2277   
2278   g_return_val_if_fail (bookmark != NULL, NULL);
2279
2280   if (!uri)
2281     return g_strdup (bookmark->description);
2282   
2283   item = g_bookmark_file_lookup_item (bookmark, uri);
2284   if (!item)
2285     {
2286       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2287                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2288                    _("No bookmark found for URI '%s'"),
2289                    uri);
2290       return NULL;
2291     }
2292   
2293   return g_strdup (item->description);
2294 }
2295
2296 /**
2297  * g_bookmark_file_set_mime_type:
2298  * @bookmark: a #GBookmarkFile
2299  * @uri: a valid URI
2300  * @mime_type: a MIME type
2301  *
2302  * Sets @mime_type as the MIME type of the bookmark for @uri.
2303  *
2304  * If a bookmark for @uri cannot be found then it is created.
2305  *
2306  * Since: 2.12
2307  */
2308 void
2309 g_bookmark_file_set_mime_type (GBookmarkFile *bookmark,
2310                                const gchar   *uri,
2311                                const gchar   *mime_type)
2312 {
2313   BookmarkItem *item;
2314   
2315   g_return_if_fail (bookmark != NULL);
2316   g_return_if_fail (uri != NULL);
2317   g_return_if_fail (mime_type != NULL);
2318   
2319   item = g_bookmark_file_lookup_item (bookmark, uri);
2320   if (!item)
2321     {
2322       item = bookmark_item_new (uri);
2323       g_bookmark_file_add_item (bookmark, item, NULL);
2324     }
2325   
2326   if (!item->metadata)
2327     item->metadata = bookmark_metadata_new ();
2328   
2329   g_free (item->metadata->mime_type);
2330   
2331   item->metadata->mime_type = g_strdup (mime_type);
2332   item->modified = time (NULL);
2333 }
2334
2335 /**
2336  * g_bookmark_file_get_mime_type:
2337  * @bookmark: a #GBookmarkFile
2338  * @uri: a valid URI
2339  * @error: return location for a #GError, or %NULL
2340  *
2341  * Retrieves the MIME type of the resource pointed by @uri.
2342  *
2343  * In the event the URI cannot be found, %NULL is returned and
2344  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.  In the
2345  * event that the MIME type cannot be found, %NULL is returned and
2346  * @error is set to #G_BOOKMARK_FILE_ERROR_INVALID_VALUE.
2347  *
2348  * Return value: a newly allocated string or %NULL if the specified
2349  *   URI cannot be found.
2350  *
2351  * Since: 2.12
2352  */
2353 gchar *
2354 g_bookmark_file_get_mime_type (GBookmarkFile  *bookmark,
2355                                const gchar    *uri,
2356                                GError        **error)
2357 {
2358   BookmarkItem *item;
2359   
2360   g_return_val_if_fail (bookmark != NULL, NULL);
2361   g_return_val_if_fail (uri != NULL, NULL);
2362   
2363   item = g_bookmark_file_lookup_item (bookmark, uri);
2364   if (!item)
2365     {
2366       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2367                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2368                    _("No bookmark found for URI '%s'"),
2369                    uri);
2370       return NULL;
2371     }
2372   
2373   if (!item->metadata)
2374     {
2375       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2376                    G_BOOKMARK_FILE_ERROR_INVALID_VALUE,
2377                    _("No MIME type defined in the bookmark for URI '%s'"),
2378                    uri);
2379       return NULL;
2380     }
2381   
2382   return g_strdup (item->metadata->mime_type);
2383 }
2384
2385 /**
2386  * g_bookmark_file_set_is_private:
2387  * @bookmark: a #GBookmarkFile
2388  * @uri: a valid URI
2389  * @is_private: %TRUE if the bookmark should be marked as private
2390  *
2391  * Sets the private flag of the bookmark for @uri.
2392  *
2393  * If a bookmark for @uri cannot be found then it is created.
2394  *
2395  * Since: 2.12
2396  */
2397 void
2398 g_bookmark_file_set_is_private (GBookmarkFile *bookmark,
2399                                 const gchar   *uri,
2400                                 gboolean       is_private)
2401 {
2402   BookmarkItem *item;
2403   
2404   g_return_if_fail (bookmark != NULL);
2405   g_return_if_fail (uri != NULL);
2406   
2407   item = g_bookmark_file_lookup_item (bookmark, uri);
2408   if (!item)
2409     {
2410       item = bookmark_item_new (uri);
2411       g_bookmark_file_add_item (bookmark, item, NULL);
2412     }
2413   
2414   if (!item->metadata)
2415     item->metadata = bookmark_metadata_new ();
2416   
2417   item->metadata->is_private = (is_private == TRUE);
2418   item->modified = time (NULL);
2419 }
2420
2421 /**
2422  * g_bookmark_file_get_is_private:
2423  * @bookmark: a #GBookmarkFile
2424  * @uri: a valid URI
2425  * @error: return location for a #GError, or %NULL
2426  *
2427  * Gets whether the private flag of the bookmark for @uri is set.
2428  *
2429  * In the event the URI cannot be found, %FALSE is returned and
2430  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.  In the
2431  * event that the private flag cannot be found, %FALSE is returned and
2432  * @error is set to #G_BOOKMARK_FILE_ERROR_INVALID_VALUE.
2433  *
2434  * Return value: %TRUE if the private flag is set, %FALSE otherwise.
2435  *
2436  * Since: 2.12
2437  */
2438 gboolean
2439 g_bookmark_file_get_is_private (GBookmarkFile  *bookmark,
2440                                 const gchar    *uri,
2441                                 GError        **error)
2442 {
2443   BookmarkItem *item;
2444   
2445   g_return_val_if_fail (bookmark != NULL, FALSE);
2446   g_return_val_if_fail (uri != NULL, FALSE);
2447   
2448   item = g_bookmark_file_lookup_item (bookmark, uri);
2449   if (!item)
2450     {
2451       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2452                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2453                    _("No bookmark found for URI '%s'"),
2454                    uri);
2455       return FALSE;
2456     }
2457   
2458   if (!item->metadata)
2459     {
2460       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2461                    G_BOOKMARK_FILE_ERROR_INVALID_VALUE,
2462                    _("No private flag has been defined in bookmark for URI '%s'"),
2463                     uri);
2464       return FALSE;
2465     }
2466   
2467   return item->metadata->is_private;
2468 }
2469
2470 /**
2471  * g_bookmark_file_set_added:
2472  * @bookmark: a #GBookmarkFile
2473  * @uri: a valid URI
2474  * @added: a timestamp or -1 to use the current time
2475  *
2476  * Sets the time the bookmark for @uri was added into @bookmark.
2477  *
2478  * If no bookmark for @uri is found then it is created.
2479  *
2480  * Since: 2.12
2481  */
2482 void
2483 g_bookmark_file_set_added (GBookmarkFile *bookmark,
2484                            const gchar   *uri,
2485                            time_t         added)
2486 {
2487   BookmarkItem *item;
2488   
2489   g_return_if_fail (bookmark != NULL);
2490   g_return_if_fail (uri != NULL);
2491   
2492   item = g_bookmark_file_lookup_item (bookmark, uri);
2493   if (!item)
2494     {
2495       item = bookmark_item_new (uri);
2496       g_bookmark_file_add_item (bookmark, item, NULL);
2497     }
2498
2499   if (added == (time_t) -1)
2500     time (&added);
2501   
2502   item->added = added;
2503   item->modified = added;
2504 }
2505
2506 /**
2507  * g_bookmark_file_get_added:
2508  * @bookmark: a #GBookmarkFile
2509  * @uri: a valid URI
2510  * @error: return location for a #GError, or %NULL
2511  *
2512  * Gets the time the bookmark for @uri was added to @bookmark
2513  *
2514  * In the event the URI cannot be found, -1 is returned and
2515  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2516  *
2517  * Return value: a timestamp
2518  *
2519  * Since: 2.12
2520  */
2521 time_t
2522 g_bookmark_file_get_added (GBookmarkFile  *bookmark,
2523                            const gchar    *uri,
2524                            GError        **error)
2525 {
2526   BookmarkItem *item;
2527   
2528   g_return_val_if_fail (bookmark != NULL, (time_t) -1);
2529   g_return_val_if_fail (uri != NULL, (time_t) -1);
2530   
2531   item = g_bookmark_file_lookup_item (bookmark, uri);
2532   if (!item)
2533     {
2534       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2535                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2536                    _("No bookmark found for URI '%s'"),
2537                    uri);
2538       return (time_t) -1;
2539     }
2540   
2541   return item->added;
2542 }
2543
2544 /**
2545  * g_bookmark_file_set_modified:
2546  * @bookmark: a #GBookmarkFile
2547  * @uri: a valid URI
2548  * @modified: a timestamp or -1 to use the current time
2549  *
2550  * Sets the last time the bookmark for @uri was last modified.
2551  *
2552  * If no bookmark for @uri is found then it is created.
2553  *
2554  * The "modified" time should only be set when the bookmark's meta-data
2555  * was actually changed.  Every function of #GBookmarkFile that
2556  * modifies a bookmark also changes the modification time, except for
2557  * g_bookmark_file_set_visited().
2558  *
2559  * Since: 2.12
2560  */
2561 void
2562 g_bookmark_file_set_modified (GBookmarkFile *bookmark,
2563                               const gchar   *uri,
2564                               time_t         modified)
2565 {
2566   BookmarkItem *item;
2567   
2568   g_return_if_fail (bookmark != NULL);
2569   g_return_if_fail (uri != NULL);
2570   
2571   item = g_bookmark_file_lookup_item (bookmark, uri);
2572   if (!item)
2573     {
2574       item = bookmark_item_new (uri);
2575       g_bookmark_file_add_item (bookmark, item, NULL);
2576     }
2577   
2578   if (modified == (time_t) -1)
2579     time (&modified);
2580   
2581   item->modified = modified;
2582 }
2583
2584 /**
2585  * g_bookmark_file_get_modified:
2586  * @bookmark: a #GBookmarkFile
2587  * @uri: a valid URI
2588  * @error: return location for a #GError, or %NULL
2589  *
2590  * Gets the time when the bookmark for @uri was last modified.
2591  *
2592  * In the event the URI cannot be found, -1 is returned and
2593  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2594  *
2595  * Return value: a timestamp
2596  *
2597  * Since: 2.12
2598  */
2599 time_t
2600 g_bookmark_file_get_modified (GBookmarkFile  *bookmark,
2601                               const gchar    *uri,
2602                               GError        **error)
2603 {
2604   BookmarkItem *item;
2605   
2606   g_return_val_if_fail (bookmark != NULL, (time_t) -1);
2607   g_return_val_if_fail (uri != NULL, (time_t) -1);
2608   
2609   item = g_bookmark_file_lookup_item (bookmark, uri);
2610   if (!item)
2611     {
2612       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2613                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2614                    _("No bookmark found for URI '%s'"),
2615                    uri);
2616       return (time_t) -1;
2617     }
2618   
2619   return item->modified;
2620 }
2621
2622 /**
2623  * g_bookmark_file_set_visited:
2624  * @bookmark: a #GBookmarkFile
2625  * @uri: a valid URI
2626  * @visited: a timestamp or -1 to use the current time
2627  *
2628  * Sets the time the bookmark for @uri was last visited.
2629  *
2630  * If no bookmark for @uri is found then it is created.
2631  *
2632  * The "visited" time should only be set if the bookmark was launched, 
2633  * either using the command line retrieved by g_bookmark_file_get_app_info()
2634  * or by the default application for the bookmark's MIME type, retrieved
2635  * using g_bookmark_file_get_mime_type().  Changing the "visited" time
2636  * does not affect the "modified" time.
2637  *
2638  * Since: 2.12
2639  */
2640 void
2641 g_bookmark_file_set_visited (GBookmarkFile *bookmark,
2642                              const gchar   *uri,
2643                              time_t         visited)
2644 {
2645   BookmarkItem *item;
2646   
2647   g_return_if_fail (bookmark != NULL);
2648   g_return_if_fail (uri != NULL);
2649   
2650   item = g_bookmark_file_lookup_item (bookmark, uri);
2651   if (!item)
2652     {
2653       item = bookmark_item_new (uri);
2654       g_bookmark_file_add_item (bookmark, item, NULL);
2655     }
2656
2657   if (visited == (time_t) -1)
2658     time (&visited);
2659   
2660   item->visited = visited;
2661 }
2662
2663 /**
2664  * g_bookmark_file_get_visited:
2665  * @bookmark: a #GBookmarkFile
2666  * @uri: a valid URI
2667  * @error: return location for a #GError, or %NULL
2668  *
2669  * Gets the time the bookmark for @uri was last visited.
2670  *
2671  * In the event the URI cannot be found, -1 is returned and
2672  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2673  *
2674  * Return value: a timestamp.
2675  *
2676  * Since: 2.12
2677  */
2678 time_t
2679 g_bookmark_file_get_visited (GBookmarkFile  *bookmark,
2680                              const gchar    *uri,
2681                              GError        **error)
2682 {
2683   BookmarkItem *item;
2684   
2685   g_return_val_if_fail (bookmark != NULL, (time_t) -1);
2686   g_return_val_if_fail (uri != NULL, (time_t) -1);
2687   
2688   item = g_bookmark_file_lookup_item (bookmark, uri);
2689   if (!item)
2690     {
2691       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2692                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2693                    _("No bookmark found for URI '%s'"),
2694                    uri);
2695       return (time_t) -1;
2696     }
2697   
2698   return item->visited;
2699 }
2700
2701 /**
2702  * g_bookmark_file_has_group:
2703  * @bookmark: a #GBookmarkFile
2704  * @uri: a valid URI
2705  * @group: the group name to be searched
2706  * @error: return location for a #GError, or %NULL
2707  *
2708  * Checks whether @group appears in the list of groups to which
2709  * the bookmark for @uri belongs to.
2710  *
2711  * In the event the URI cannot be found, %FALSE is returned and
2712  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2713  *
2714  * Return value: %TRUE if @group was found.
2715  *
2716  * Since: 2.12
2717  */
2718 gboolean
2719 g_bookmark_file_has_group (GBookmarkFile  *bookmark,
2720                            const gchar    *uri,
2721                            const gchar    *group,
2722                            GError        **error)
2723 {
2724   BookmarkItem *item;
2725   GList *l;
2726   
2727   g_return_val_if_fail (bookmark != NULL, FALSE);
2728   g_return_val_if_fail (uri != NULL, FALSE);
2729   
2730   item = g_bookmark_file_lookup_item (bookmark, uri);
2731   if (!item)
2732     {
2733       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2734                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2735                    _("No bookmark found for URI '%s'"),
2736                    uri);
2737       return FALSE;
2738     }
2739   
2740   if (!item->metadata)
2741     return FALSE;
2742    
2743   for (l = item->metadata->groups; l != NULL; l = l->next)
2744     {
2745       if (strcmp (l->data, group) == 0)
2746         return TRUE;
2747     }
2748   
2749   return FALSE;
2750
2751 }
2752
2753 /**
2754  * g_bookmark_file_add_group:
2755  * @bookmark: a #GBookmarkFile
2756  * @uri: a valid URI
2757  * @group: the group name to be added
2758  *
2759  * Adds @group to the list of groups to which the bookmark for @uri
2760  * belongs to.
2761  *
2762  * If no bookmark for @uri is found then it is created.
2763  *
2764  * Since: 2.12
2765  */
2766 void
2767 g_bookmark_file_add_group (GBookmarkFile *bookmark,
2768                            const gchar   *uri,
2769                            const gchar   *group)
2770 {
2771   BookmarkItem *item;
2772   
2773   g_return_if_fail (bookmark != NULL);
2774   g_return_if_fail (uri != NULL);
2775   g_return_if_fail (group != NULL && group[0] != '\0');
2776   
2777   item = g_bookmark_file_lookup_item (bookmark, uri);
2778   if (!item)
2779     {
2780       item = bookmark_item_new (uri);
2781       g_bookmark_file_add_item (bookmark, item, NULL);
2782     }
2783   
2784   if (!item->metadata)
2785     item->metadata = bookmark_metadata_new ();
2786   
2787   if (!g_bookmark_file_has_group (bookmark, uri, group, NULL))
2788     {
2789       item->metadata->groups = g_list_prepend (item->metadata->groups,
2790                                                g_strdup (group));
2791       
2792       item->modified = time (NULL);
2793     }
2794 }
2795
2796 /**
2797  * g_bookmark_file_remove_group:
2798  * @bookmark: a #GBookmarkFile
2799  * @uri: a valid URI
2800  * @group: the group name to be removed
2801  * @error: return location for a #GError, or %NULL
2802  *
2803  * Removes @group from the list of groups to which the bookmark
2804  * for @uri belongs to.
2805  *
2806  * In the event the URI cannot be found, %FALSE is returned and
2807  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2808  * In the event no group was defined, %FALSE is returned and
2809  * @error is set to #G_BOOKMARK_FILE_ERROR_INVALID_VALUE.
2810  *
2811  * Return value: %TRUE if @group was successfully removed.
2812  *
2813  * Since: 2.12
2814  */
2815 gboolean
2816 g_bookmark_file_remove_group (GBookmarkFile  *bookmark,
2817                               const gchar    *uri,
2818                               const gchar    *group,
2819                               GError        **error)
2820 {
2821   BookmarkItem *item;
2822   GList *l;
2823   
2824   g_return_val_if_fail (bookmark != NULL, FALSE);
2825   g_return_val_if_fail (uri != NULL, FALSE);
2826   
2827   item = g_bookmark_file_lookup_item (bookmark, uri);
2828   if (!item)
2829     {
2830       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2831                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2832                    _("No bookmark found for URI '%s'"),
2833                    uri);
2834       return FALSE;
2835     }
2836   
2837   if (!item->metadata)
2838     {
2839       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2840                    G_BOOKMARK_FILE_ERROR_INVALID_VALUE,
2841                    _("No groups set in bookmark for URI '%s'"),
2842                    uri);
2843       return FALSE;
2844     }
2845   
2846   for (l = item->metadata->groups; l != NULL; l = l->next)
2847     {
2848       if (strcmp (l->data, group) == 0)
2849         {
2850           item->metadata->groups = g_list_remove_link (item->metadata->groups, l);
2851           g_free (l->data);
2852           g_list_free_1 (l);
2853           
2854           item->modified = time (NULL);          
2855           
2856           return TRUE;
2857         }
2858     }
2859   
2860   return FALSE;
2861 }
2862
2863 /**
2864  * g_bookmark_file_set_groups:
2865  * @bookmark: a #GBookmarkFile
2866  * @uri: an item's URI
2867  * @groups: (allow-none): an array of group names, or %NULL to remove all groups
2868  * @length: number of group name values in @groups
2869  *
2870  * Sets a list of group names for the item with URI @uri.  Each previously
2871  * set group name list is removed.
2872  *
2873  * If @uri cannot be found then an item for it is created.
2874  *
2875  * Since: 2.12
2876  */
2877 void
2878 g_bookmark_file_set_groups (GBookmarkFile  *bookmark,
2879                             const gchar    *uri,
2880                             const gchar   **groups,
2881                             gsize           length)
2882 {
2883   BookmarkItem *item;
2884   gsize i;
2885   
2886   g_return_if_fail (bookmark != NULL);
2887   g_return_if_fail (uri != NULL);
2888   g_return_if_fail (groups != NULL);
2889   
2890   item = g_bookmark_file_lookup_item (bookmark, uri);
2891   if (!item)
2892     {
2893       item = bookmark_item_new (uri);
2894       g_bookmark_file_add_item (bookmark, item, NULL);
2895     }
2896   
2897   if (!item->metadata)
2898     item->metadata = bookmark_metadata_new ();
2899
2900   g_list_free_full (item->metadata->groups, g_free);
2901   item->metadata->groups = NULL;
2902   
2903   if (groups)
2904     {
2905       for (i = 0; groups[i] != NULL && i < length; i++)
2906         item->metadata->groups = g_list_append (item->metadata->groups,
2907                                                 g_strdup (groups[i]));
2908     }
2909
2910   item->modified = time (NULL);
2911 }
2912
2913 /**
2914  * g_bookmark_file_get_groups:
2915  * @bookmark: a #GBookmarkFile
2916  * @uri: a valid URI
2917  * @length: (allow-none) (out): return location for the length of the returned string, or %NULL
2918  * @error: return location for a #GError, or %NULL
2919  *
2920  * Retrieves the list of group names of the bookmark for @uri.
2921  *
2922  * In the event the URI cannot be found, %NULL is returned and
2923  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2924  *
2925  * The returned array is %NULL terminated, so @length may optionally
2926  * be %NULL.
2927  *
2928  * Return value: (array length=length) (transfer full): a newly allocated %NULL-terminated array of group names.
2929  *   Use g_strfreev() to free it.
2930  *
2931  * Since: 2.12
2932  */
2933 gchar **
2934 g_bookmark_file_get_groups (GBookmarkFile  *bookmark,
2935                             const gchar    *uri,
2936                             gsize          *length,
2937                             GError        **error)
2938 {
2939   BookmarkItem *item;
2940   GList *l;
2941   gsize len, i;
2942   gchar **retval;
2943   
2944   g_return_val_if_fail (bookmark != NULL, NULL);
2945   g_return_val_if_fail (uri != NULL, NULL);
2946   
2947   item = g_bookmark_file_lookup_item (bookmark, uri);
2948   if (!item)
2949     {
2950       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2951                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2952                    _("No bookmark found for URI '%s'"),
2953                    uri);
2954       return NULL;
2955     }
2956   
2957   if (!item->metadata)
2958     {
2959       if (length)
2960         *length = 0;
2961       
2962       return NULL;
2963     }
2964   
2965   len = g_list_length (item->metadata->groups);
2966   retval = g_new0 (gchar *, len + 1);
2967   for (l = g_list_last (item->metadata->groups), i = 0;
2968        l != NULL;
2969        l = l->prev)
2970     {
2971       gchar *group_name = (gchar *) l->data;
2972       
2973       g_warn_if_fail (group_name != NULL);
2974       
2975       retval[i++] = g_strdup (group_name);
2976     }
2977   retval[i] = NULL;
2978   
2979   if (length)
2980     *length = len;
2981   
2982   return retval;
2983 }
2984
2985 /**
2986  * g_bookmark_file_add_application:
2987  * @bookmark: a #GBookmarkFile
2988  * @uri: a valid URI
2989  * @name: (allow-none): the name of the application registering the bookmark
2990  *   or %NULL
2991  * @exec: (allow-none): command line to be used to launch the bookmark or %NULL
2992  *
2993  * Adds the application with @name and @exec to the list of
2994  * applications that have registered a bookmark for @uri into
2995  * @bookmark.
2996  *
2997  * Every bookmark inside a #GBookmarkFile must have at least an
2998  * application registered.  Each application must provide a name, a
2999  * command line useful for launching the bookmark, the number of times
3000  * the bookmark has been registered by the application and the last
3001  * time the application registered this bookmark.
3002  *
3003  * If @name is %NULL, the name of the application will be the
3004  * same returned by g_get_application_name(); if @exec is %NULL, the
3005  * command line will be a composition of the program name as
3006  * returned by g_get_prgname() and the "\%u" modifier, which will be
3007  * expanded to the bookmark's URI.
3008  *
3009  * This function will automatically take care of updating the
3010  * registrations count and timestamping in case an application
3011  * with the same @name had already registered a bookmark for
3012  * @uri inside @bookmark.
3013  *
3014  * If no bookmark for @uri is found, one is created.
3015  *
3016  * Since: 2.12
3017  */
3018 void
3019 g_bookmark_file_add_application (GBookmarkFile *bookmark,
3020                                  const gchar   *uri,
3021                                  const gchar   *name,
3022                                  const gchar   *exec)
3023 {
3024   BookmarkItem *item;
3025   gchar *app_name, *app_exec;
3026   
3027   g_return_if_fail (bookmark != NULL);
3028   g_return_if_fail (uri != NULL);
3029   
3030   item = g_bookmark_file_lookup_item (bookmark, uri);
3031   if (!item)
3032     {
3033       item = bookmark_item_new (uri);
3034       g_bookmark_file_add_item (bookmark, item, NULL);
3035     }
3036   
3037   if (name && name[0] != '\0')
3038     app_name = g_strdup (name);
3039   else
3040     app_name = g_strdup (g_get_application_name ());
3041   
3042   if (exec && exec[0] != '\0')
3043     app_exec = g_strdup (exec);
3044   else
3045     app_exec = g_strjoin (" ", g_get_prgname(), "%u", NULL);
3046
3047   g_bookmark_file_set_app_info (bookmark, uri,
3048                                 app_name,
3049                                 app_exec,
3050                                 -1,
3051                                 (time_t) -1,
3052                                 NULL);
3053   
3054   g_free (app_exec);
3055   g_free (app_name);
3056 }
3057
3058 /**
3059  * g_bookmark_file_remove_application:
3060  * @bookmark: a #GBookmarkFile
3061  * @uri: a valid URI
3062  * @name: the name of the application
3063  * @error: return location for a #GError or %NULL
3064  *
3065  * Removes application registered with @name from the list of applications
3066  * that have registered a bookmark for @uri inside @bookmark.
3067  *
3068  * In the event the URI cannot be found, %FALSE is returned and
3069  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3070  * In the event that no application with name @app_name has registered
3071  * a bookmark for @uri,  %FALSE is returned and error is set to
3072  * #G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED.
3073  *
3074  * Return value: %TRUE if the application was successfully removed.
3075  *
3076  * Since: 2.12
3077  */
3078 gboolean
3079 g_bookmark_file_remove_application (GBookmarkFile  *bookmark,
3080                                     const gchar    *uri,
3081                                     const gchar    *name,
3082                                     GError        **error)
3083 {
3084   GError *set_error;
3085   gboolean retval;
3086     
3087   g_return_val_if_fail (bookmark != NULL, FALSE);
3088   g_return_val_if_fail (uri != NULL, FALSE);
3089   g_return_val_if_fail (name != NULL, FALSE);
3090   
3091   set_error = NULL;
3092   retval = g_bookmark_file_set_app_info (bookmark, uri,
3093                                          name,
3094                                          "",
3095                                          0,
3096                                          (time_t) -1,
3097                                          &set_error);
3098   if (set_error)
3099     {
3100       g_propagate_error (error, set_error);
3101       
3102       return FALSE;
3103     }
3104   
3105   return retval;
3106 }
3107
3108 /**
3109  * g_bookmark_file_has_application:
3110  * @bookmark: a #GBookmarkFile
3111  * @uri: a valid URI
3112  * @name: the name of the application
3113  * @error: return location for a #GError or %NULL
3114  *
3115  * Checks whether the bookmark for @uri inside @bookmark has been
3116  * registered by application @name.
3117  *
3118  * In the event the URI cannot be found, %FALSE is returned and
3119  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3120  *
3121  * Return value: %TRUE if the application @name was found
3122  *
3123  * Since: 2.12
3124  */
3125 gboolean
3126 g_bookmark_file_has_application (GBookmarkFile  *bookmark,
3127                                  const gchar    *uri,
3128                                  const gchar    *name,
3129                                  GError        **error)
3130 {
3131   BookmarkItem *item;
3132   
3133   g_return_val_if_fail (bookmark != NULL, FALSE);
3134   g_return_val_if_fail (uri != NULL, FALSE);
3135   g_return_val_if_fail (name != NULL, FALSE);
3136   
3137   item = g_bookmark_file_lookup_item (bookmark, uri);
3138   if (!item)
3139     {
3140       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3141                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3142                    _("No bookmark found for URI '%s'"),
3143                    uri);
3144       return FALSE;
3145     }
3146   
3147   return (NULL != bookmark_item_lookup_app_info (item, name));
3148 }
3149
3150 /**
3151  * g_bookmark_file_set_app_info:
3152  * @bookmark: a #GBookmarkFile
3153  * @uri: a valid URI
3154  * @name: an application's name
3155  * @exec: an application's command line
3156  * @count: the number of registrations done for this application
3157  * @stamp: the time of the last registration for this application
3158  * @error: return location for a #GError or %NULL
3159  *
3160  * Sets the meta-data of application @name inside the list of
3161  * applications that have registered a bookmark for @uri inside
3162  * @bookmark.
3163  *
3164  * You should rarely use this function; use g_bookmark_file_add_application()
3165  * and g_bookmark_file_remove_application() instead.
3166  *
3167  * @name can be any UTF-8 encoded string used to identify an
3168  * application.
3169  * @exec can have one of these two modifiers: "\%f", which will
3170  * be expanded as the local file name retrieved from the bookmark's
3171  * URI; "\%u", which will be expanded as the bookmark's URI.
3172  * The expansion is done automatically when retrieving the stored
3173  * command line using the g_bookmark_file_get_app_info() function.
3174  * @count is the number of times the application has registered the
3175  * bookmark; if is < 0, the current registration count will be increased
3176  * by one, if is 0, the application with @name will be removed from
3177  * the list of registered applications.
3178  * @stamp is the Unix time of the last registration; if it is -1, the
3179  * current time will be used.
3180  *
3181  * If you try to remove an application by setting its registration count to
3182  * zero, and no bookmark for @uri is found, %FALSE is returned and
3183  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND; similarly,
3184  * in the event that no application @name has registered a bookmark
3185  * for @uri,  %FALSE is returned and error is set to
3186  * #G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED.  Otherwise, if no bookmark
3187  * for @uri is found, one is created.
3188  *
3189  * Return value: %TRUE if the application's meta-data was successfully
3190  *   changed.
3191  *
3192  * Since: 2.12
3193  */
3194 gboolean
3195 g_bookmark_file_set_app_info (GBookmarkFile  *bookmark,
3196                               const gchar    *uri,
3197                               const gchar    *name,
3198                               const gchar    *exec,
3199                               gint            count,
3200                               time_t          stamp,
3201                               GError        **error)
3202 {
3203   BookmarkItem *item;
3204   BookmarkAppInfo *ai;
3205   
3206   g_return_val_if_fail (bookmark != NULL, FALSE);
3207   g_return_val_if_fail (uri != NULL, FALSE);
3208   g_return_val_if_fail (name != NULL, FALSE);
3209   g_return_val_if_fail (exec != NULL, FALSE);
3210   
3211   item = g_bookmark_file_lookup_item (bookmark, uri);
3212   if (!item)
3213     {
3214       if (count == 0)
3215         {
3216           g_set_error (error, G_BOOKMARK_FILE_ERROR,
3217                        G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3218                        _("No bookmark found for URI '%s'"),
3219                        uri);
3220           return FALSE;
3221         }
3222       else
3223         {
3224           item = bookmark_item_new (uri);
3225           g_bookmark_file_add_item (bookmark, item, NULL);
3226         }
3227     }
3228   
3229   if (!item->metadata)
3230     item->metadata = bookmark_metadata_new ();
3231
3232   ai = bookmark_item_lookup_app_info (item, name);
3233   if (!ai)
3234     {
3235       if (count == 0)
3236         {
3237           g_set_error (error, G_BOOKMARK_FILE_ERROR,
3238                        G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED,
3239                        _("No application with name '%s' registered a bookmark for '%s'"),
3240                        name,
3241                        uri);
3242           return FALSE;
3243         }
3244       else
3245         {
3246           ai = bookmark_app_info_new (name);
3247           
3248           item->metadata->applications = g_list_prepend (item->metadata->applications, ai);
3249           g_hash_table_replace (item->metadata->apps_by_name, ai->name, ai);
3250         }
3251     }
3252
3253   if (count == 0)
3254     {
3255       item->metadata->applications = g_list_remove (item->metadata->applications, ai);
3256       g_hash_table_remove (item->metadata->apps_by_name, ai->name);
3257       bookmark_app_info_free (ai);
3258
3259       item->modified = time (NULL);
3260           
3261       return TRUE;
3262     }
3263   else if (count > 0)
3264     ai->count = count;
3265   else
3266     ai->count += 1;
3267       
3268   if (stamp != (time_t) -1)
3269     ai->stamp = stamp;
3270   else
3271     ai->stamp = time (NULL);
3272   
3273   if (exec && exec[0] != '\0')
3274     {
3275       g_free (ai->exec);
3276       ai->exec = g_shell_quote (exec);
3277     }
3278   
3279   item->modified = time (NULL);
3280   
3281   return TRUE;
3282 }
3283
3284 /* expands the application's command line */
3285 static gchar *
3286 expand_exec_line (const gchar *exec_fmt,
3287                   const gchar *uri)
3288 {
3289   GString *exec;
3290   gchar ch;
3291   
3292   exec = g_string_sized_new (512);
3293   while ((ch = *exec_fmt++) != '\0')
3294    {
3295      if (ch != '%')
3296        {
3297          exec = g_string_append_c (exec, ch);
3298          continue;
3299        }
3300      
3301      ch = *exec_fmt++;
3302      switch (ch)
3303        {
3304        case '\0':
3305          goto out;
3306        case 'U':
3307        case 'u':
3308          g_string_append (exec, uri);
3309          break;
3310        case 'F':
3311        case 'f':
3312          {
3313            gchar *file = g_filename_from_uri (uri, NULL, NULL);
3314            if (file)
3315              {
3316                g_string_append (exec, file);
3317                g_free (file);
3318              }
3319            else
3320              {
3321                g_string_free (exec, TRUE);
3322                return NULL;
3323              }
3324          }
3325          break;
3326        case '%':
3327        default:
3328          exec = g_string_append_c (exec, ch);
3329          break;
3330        }
3331    }
3332    
3333  out:
3334   return g_string_free (exec, FALSE);
3335 }
3336
3337 /**
3338  * g_bookmark_file_get_app_info:
3339  * @bookmark: a #GBookmarkFile
3340  * @uri: a valid URI
3341  * @name: an application's name
3342  * @exec: (allow-none) (out): return location for the command line of the application, or %NULL
3343  * @count: (allow-none) (out): return location for the registration count, or %NULL
3344  * @stamp: (allow-none) (out): return location for the last registration time, or %NULL
3345  * @error: return location for a #GError, or %NULL
3346  *
3347  * Gets the registration informations of @app_name for the bookmark for
3348  * @uri.  See g_bookmark_file_set_app_info() for more informations about
3349  * the returned data.
3350  *
3351  * The string returned in @app_exec must be freed.
3352  *
3353  * In the event the URI cannot be found, %FALSE is returned and
3354  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.  In the
3355  * event that no application with name @app_name has registered a bookmark
3356  * for @uri,  %FALSE is returned and error is set to
3357  * #G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED. In the event that unquoting
3358  * the command line fails, an error of the #G_SHELL_ERROR domain is
3359  * set and %FALSE is returned.
3360  *
3361  * Return value: %TRUE on success.
3362  *
3363  * Since: 2.12
3364  */
3365 gboolean
3366 g_bookmark_file_get_app_info (GBookmarkFile  *bookmark,
3367                               const gchar    *uri,
3368                               const gchar    *name,
3369                               gchar         **exec,
3370                               guint          *count,
3371                               time_t         *stamp,
3372                               GError        **error)
3373 {
3374   BookmarkItem *item;
3375   BookmarkAppInfo *ai;
3376   
3377   g_return_val_if_fail (bookmark != NULL, FALSE);
3378   g_return_val_if_fail (uri != NULL, FALSE);
3379   g_return_val_if_fail (name != NULL, FALSE);
3380   
3381   item = g_bookmark_file_lookup_item (bookmark, uri);
3382   if (!item)
3383     {
3384       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3385                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3386                    _("No bookmark found for URI '%s'"),
3387                    uri);
3388       return FALSE;
3389     }
3390   
3391   ai = bookmark_item_lookup_app_info (item, name);
3392   if (!ai)
3393     {
3394       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3395                    G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED,
3396                    _("No application with name '%s' registered a bookmark for '%s'"),
3397                    name,
3398                    uri);
3399       return FALSE;
3400     }
3401   
3402   if (exec)
3403     {
3404       GError *unquote_error = NULL;
3405       gchar *command_line;
3406       
3407       command_line = g_shell_unquote (ai->exec, &unquote_error);
3408       if (unquote_error)
3409         {
3410           g_propagate_error (error, unquote_error);
3411           return FALSE;
3412         }
3413
3414       *exec = expand_exec_line (command_line, uri);
3415       if (!*exec)
3416         {
3417           g_set_error (error, G_BOOKMARK_FILE_ERROR,
3418                        G_BOOKMARK_FILE_ERROR_INVALID_URI,
3419                        _("Failed to expand exec line '%s' with URI '%s'"),
3420                      ai->exec, uri);
3421           g_free (command_line);
3422
3423           return FALSE;
3424         }
3425       else
3426         g_free (command_line);
3427     } 
3428
3429   if (count)
3430     *count = ai->count;
3431   
3432   if (stamp)
3433     *stamp = ai->stamp;
3434   
3435   return TRUE;
3436 }
3437
3438 /**
3439  * g_bookmark_file_get_applications:
3440  * @bookmark: a #GBookmarkFile
3441  * @uri: a valid URI
3442  * @length: (allow-none) (out): return location of the length of the returned list, or %NULL
3443  * @error: return location for a #GError, or %NULL
3444  *
3445  * Retrieves the names of the applications that have registered the
3446  * bookmark for @uri.
3447  * 
3448  * In the event the URI cannot be found, %NULL is returned and
3449  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3450  *
3451  * Return value: (array length=length) (transfer full): a newly allocated %NULL-terminated array of strings.
3452  *   Use g_strfreev() to free it.
3453  *
3454  * Since: 2.12
3455  */
3456 gchar **
3457 g_bookmark_file_get_applications (GBookmarkFile  *bookmark,
3458                                   const gchar    *uri,
3459                                   gsize          *length,
3460                                   GError        **error)
3461 {
3462   BookmarkItem *item;
3463   GList *l;
3464   gchar **apps;
3465   gsize i, n_apps;
3466   
3467   g_return_val_if_fail (bookmark != NULL, NULL);
3468   g_return_val_if_fail (uri != NULL, NULL);
3469   
3470   item = g_bookmark_file_lookup_item (bookmark, uri);
3471   if (!item)
3472     {
3473       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3474                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3475                    _("No bookmark found for URI '%s'"),
3476                    uri);
3477       return NULL;
3478     }
3479   
3480   if (!item->metadata)
3481     {      
3482       if (length)
3483         *length = 0;
3484       
3485       return NULL;
3486     }
3487   
3488   n_apps = g_list_length (item->metadata->applications);
3489   apps = g_new0 (gchar *, n_apps + 1);
3490   
3491   for (l = g_list_last (item->metadata->applications), i = 0;
3492        l != NULL;
3493        l = l->prev)
3494     {
3495       BookmarkAppInfo *ai;
3496       
3497       ai = (BookmarkAppInfo *) l->data;
3498       
3499       g_warn_if_fail (ai != NULL);
3500       g_warn_if_fail (ai->name != NULL);
3501       
3502       apps[i++] = g_strdup (ai->name);
3503     }
3504   apps[i] = NULL;
3505   
3506   if (length)
3507     *length = i;
3508   
3509   return apps;
3510 }
3511
3512 /**
3513  * g_bookmark_file_get_size:
3514  * @bookmark: a #GBookmarkFile
3515  * 
3516  * Gets the number of bookmarks inside @bookmark.
3517  * 
3518  * Return value: the number of bookmarks
3519  *
3520  * Since: 2.12
3521  */
3522 gint
3523 g_bookmark_file_get_size (GBookmarkFile *bookmark)
3524 {
3525   g_return_val_if_fail (bookmark != NULL, 0);
3526
3527   return g_list_length (bookmark->items);
3528 }
3529
3530 /**
3531  * g_bookmark_file_move_item:
3532  * @bookmark: a #GBookmarkFile
3533  * @old_uri: a valid URI
3534  * @new_uri: (allow-none): a valid URI, or %NULL
3535  * @error: return location for a #GError or %NULL
3536  *
3537  * Changes the URI of a bookmark item from @old_uri to @new_uri.  Any
3538  * existing bookmark for @new_uri will be overwritten.  If @new_uri is
3539  * %NULL, then the bookmark is removed.
3540  *
3541  * In the event the URI cannot be found, %FALSE is returned and
3542  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3543  *
3544  * Return value: %TRUE if the URI was successfully changed
3545  *
3546  * Since: 2.12
3547  */
3548 gboolean
3549 g_bookmark_file_move_item (GBookmarkFile  *bookmark,
3550                            const gchar    *old_uri,
3551                            const gchar    *new_uri,
3552                            GError        **error)
3553 {
3554   BookmarkItem *item;
3555   GError *remove_error;
3556   
3557   g_return_val_if_fail (bookmark != NULL, FALSE);
3558   g_return_val_if_fail (old_uri != NULL, FALSE);
3559
3560   item = g_bookmark_file_lookup_item (bookmark, old_uri);
3561   if (!item)
3562     {
3563       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3564                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3565                    _("No bookmark found for URI '%s'"),
3566                    old_uri);
3567       return FALSE;
3568     }
3569
3570   if (new_uri && new_uri[0] != '\0')
3571     {
3572       if (g_bookmark_file_has_item (bookmark, new_uri))
3573         {
3574           remove_error = NULL;
3575           g_bookmark_file_remove_item (bookmark, new_uri, &remove_error);
3576           if (remove_error)
3577             {
3578               g_propagate_error (error, remove_error);
3579               
3580               return FALSE;
3581             }
3582         }
3583
3584       g_hash_table_steal (bookmark->items_by_uri, item->uri);
3585       
3586       g_free (item->uri);
3587       item->uri = g_strdup (new_uri);
3588       item->modified = time (NULL);
3589
3590       g_hash_table_replace (bookmark->items_by_uri, item->uri, item);
3591
3592       return TRUE;
3593     }
3594   else
3595     {
3596       remove_error = NULL;
3597       g_bookmark_file_remove_item (bookmark, old_uri, &remove_error);
3598       if (remove_error)
3599         {
3600           g_propagate_error (error, remove_error);
3601           
3602           return FALSE;
3603         }
3604
3605       return TRUE;
3606     }
3607 }
3608
3609 /**
3610  * g_bookmark_file_set_icon:
3611  * @bookmark: a #GBookmarkFile
3612  * @uri: a valid URI
3613  * @href: (allow-none): the URI of the icon for the bookmark, or %NULL
3614  * @mime_type: the MIME type of the icon for the bookmark
3615  *
3616  * Sets the icon for the bookmark for @uri. If @href is %NULL, unsets
3617  * the currently set icon. @href can either be a full URL for the icon
3618  * file or the icon name following the Icon Naming specification.
3619  *
3620  * If no bookmark for @uri is found one is created.
3621  *
3622  * Since: 2.12
3623  */
3624 void
3625 g_bookmark_file_set_icon (GBookmarkFile *bookmark,
3626                           const gchar   *uri,
3627                           const gchar   *href,
3628                           const gchar   *mime_type)
3629 {
3630   BookmarkItem *item;
3631   
3632   g_return_if_fail (bookmark != NULL);
3633   g_return_if_fail (uri != NULL);
3634
3635   item = g_bookmark_file_lookup_item (bookmark, uri);
3636   if (!item)
3637     {
3638       item = bookmark_item_new (uri);
3639       g_bookmark_file_add_item (bookmark, item, NULL);
3640     }
3641   
3642   if (!item->metadata)
3643     item->metadata = bookmark_metadata_new ();
3644   
3645   g_free (item->metadata->icon_href);
3646   g_free (item->metadata->icon_mime);
3647   
3648   item->metadata->icon_href = g_strdup (href);
3649   
3650   if (mime_type && mime_type[0] != '\0')
3651     item->metadata->icon_mime = g_strdup (mime_type);
3652   else
3653     item->metadata->icon_mime = g_strdup ("application/octet-stream");
3654   
3655   item->modified = time (NULL);
3656 }
3657
3658 /**
3659  * g_bookmark_file_get_icon:
3660  * @bookmark: a #GBookmarkFile
3661  * @uri: a valid URI
3662  * @href: (allow-none) (out): return location for the icon's location or %NULL
3663  * @mime_type: (allow-none) (out): return location for the icon's MIME type or %NULL
3664  * @error: return location for a #GError or %NULL
3665  *
3666  * Gets the icon of the bookmark for @uri.
3667  *
3668  * In the event the URI cannot be found, %FALSE is returned and
3669  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3670  *
3671  * Return value: %TRUE if the icon for the bookmark for the URI was found.
3672  *   You should free the returned strings.
3673  *
3674  * Since: 2.12
3675  */
3676 gboolean
3677 g_bookmark_file_get_icon (GBookmarkFile  *bookmark,
3678                           const gchar    *uri,
3679                           gchar         **href,
3680                           gchar         **mime_type,
3681                           GError        **error)
3682 {
3683   BookmarkItem *item;
3684   
3685   g_return_val_if_fail (bookmark != NULL, FALSE);
3686   g_return_val_if_fail (uri != NULL, FALSE);
3687   
3688   item = g_bookmark_file_lookup_item (bookmark, uri);
3689   if (!item)
3690     {
3691       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3692                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3693                    _("No bookmark found for URI '%s'"),
3694                    uri);
3695       return FALSE;
3696     }
3697   
3698   if ((!item->metadata) || (!item->metadata->icon_href))
3699     return FALSE;
3700   
3701   if (href)
3702     *href = g_strdup (item->metadata->icon_href);
3703   
3704   if (mime_type)
3705     *mime_type = g_strdup (item->metadata->icon_mime);
3706   
3707   return TRUE;
3708 }