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