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