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