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