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