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