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