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