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