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