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