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