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