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