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