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