(bookmark_app_info_dump): Escape strings before dumping
[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   gchar *name, *exec;
254
255   g_assert (app_info != NULL);
256
257   if (app_info->count == 0)
258     return NULL;
259
260   name = g_markup_escape_text (app_info->name, -1);
261   exec = g_markup_escape_text (app_info->exec, -1);
262  
263   retval = g_strdup_printf ("          <%s:%s %s=\"%s\" %s=\"%s\" %s=\"%ld\" %s=\"%u\"/>\n",
264                             BOOKMARK_NAMESPACE_NAME,
265                             BOOKMARK_APPLICATION_ELEMENT,
266                             BOOKMARK_NAME_ATTRIBUTE, name,
267                             BOOKMARK_EXEC_ATTRIBUTE, exec,
268                             BOOKMARK_TIMESTAMP_ATTRIBUTE, (time_t) app_info->stamp,
269                             BOOKMARK_COUNT_ATTRIBUTE, app_info->count);
270
271   g_free (name);
272   g_free (exec);
273
274   return retval;
275 }
276
277
278 /***********************
279  * BookmarkMetadata    *
280  *                     *
281  * Metadata storage    *
282  ***********************/
283 static BookmarkMetadata *
284 bookmark_metadata_new (void)
285 {
286   BookmarkMetadata *retval;
287   
288   retval = g_slice_new (BookmarkMetadata);
289
290   retval->mime_type = NULL;
291   
292   retval->groups = NULL;
293   
294   retval->applications = NULL;
295   retval->apps_by_name = g_hash_table_new_full (g_str_hash,
296                                                 g_str_equal,
297                                                 NULL,
298                                                 NULL);
299   
300   retval->is_private = FALSE;
301   
302   retval->icon_href = NULL;
303   retval->icon_mime = NULL;
304   
305   return retval;
306 }
307
308 static void
309 bookmark_metadata_free (BookmarkMetadata *metadata)
310 {
311   if (!metadata)
312     return;
313   
314   if (metadata->mime_type)
315     g_free (metadata->mime_type);
316     
317   if (metadata->groups)
318     {
319       g_list_foreach (metadata->groups,
320                       (GFunc) g_free,
321                       NULL);
322       g_list_free (metadata->groups);
323     }
324   
325   if (metadata->applications)
326     {
327       g_list_foreach (metadata->applications,
328                       (GFunc) bookmark_app_info_free,
329                       NULL);
330       g_list_free (metadata->applications);
331       
332       g_hash_table_destroy (metadata->apps_by_name);
333     }
334   
335   if (metadata->icon_href)
336     g_free (metadata->icon_href);
337   
338   if (metadata->icon_mime)
339     g_free (metadata->icon_mime);
340   
341   g_slice_free (BookmarkMetadata, metadata);
342 }
343
344 static gchar *
345 bookmark_metadata_dump (BookmarkMetadata *metadata)
346 {
347   GString *retval;
348  
349   if (!metadata->applications)
350     return NULL;
351   
352   retval = g_string_new (NULL);
353   
354   /* metadata container */
355   g_string_append_printf (retval,
356                           "      <%s %s=\"%s\">\n",
357                           XBEL_METADATA_ELEMENT,
358                           XBEL_OWNER_ATTRIBUTE, BOOKMARK_METADATA_OWNER);
359   
360   /* mime type */
361   if (metadata->mime_type)
362     g_string_append_printf (retval,
363                             "        <%s:%s %s=\"%s\"/>\n",
364                             MIME_NAMESPACE_NAME,
365                             MIME_TYPE_ELEMENT,
366                             MIME_TYPE_ATTRIBUTE, metadata->mime_type);
367   
368   if (metadata->groups)
369     {
370       GList *l;
371       
372       /* open groups container */
373       g_string_append_printf (retval,
374                               "        <%s:%s>\n",
375                               BOOKMARK_NAMESPACE_NAME,
376                               BOOKMARK_GROUPS_ELEMENT);
377       
378       for (l = g_list_last (metadata->groups); l != NULL; l = l->prev)
379         {
380           gchar *group_name;
381
382           group_name = g_markup_escape_text ((gchar *) l->data, -1);
383           g_string_append_printf (retval,
384                                   "          <%s:%s>%s</%s:%s>\n",
385                                   BOOKMARK_NAMESPACE_NAME,
386                                   BOOKMARK_GROUP_ELEMENT,
387                                   group_name,
388                                   BOOKMARK_NAMESPACE_NAME,
389                                   BOOKMARK_GROUP_ELEMENT);
390           g_free (group_name);
391         }
392       
393       /* close groups container */
394       g_string_append_printf (retval,
395                               "        </%s:%s>\n",
396                               BOOKMARK_NAMESPACE_NAME,
397                               BOOKMARK_GROUPS_ELEMENT);
398     }
399   
400   if (metadata->applications)
401     {
402       GList *l;
403       
404       /* open applications container */
405       g_string_append_printf (retval,
406                               "        <%s:%s>\n",
407                               BOOKMARK_NAMESPACE_NAME,
408                               BOOKMARK_APPLICATIONS_ELEMENT);
409       
410       for (l = g_list_last (metadata->applications); l != NULL; l = l->prev)
411         {
412           BookmarkAppInfo *app_info = (BookmarkAppInfo *) l->data;
413           gchar *app_data;
414
415           g_assert (app_info != NULL);
416           
417           app_data = bookmark_app_info_dump (app_info);
418
419           if (app_data)
420             {
421               retval = g_string_append (retval, app_data);
422
423               g_free (app_data);
424             }
425         }
426       
427       /* close applications container */
428       g_string_append_printf (retval,
429                               "        </%s:%s>\n",
430                               BOOKMARK_NAMESPACE_NAME,
431                               BOOKMARK_APPLICATIONS_ELEMENT);
432     }
433   
434   /* icon */
435   if (metadata->icon_href)
436     {
437       if (!metadata->icon_mime)
438         metadata->icon_mime = g_strdup ("application/octet-stream");
439       
440       g_string_append_printf (retval,
441                               "       <%s:%s %s=\"%s\" %s=\"%s\"/>\n",
442                               BOOKMARK_NAMESPACE_NAME,
443                               BOOKMARK_ICON_ELEMENT,
444                               BOOKMARK_HREF_ATTRIBUTE, metadata->icon_href,
445                               BOOKMARK_TYPE_ATTRIBUTE, metadata->icon_mime);
446     }
447   
448   /* private hint */
449   if (metadata->is_private)
450     g_string_append_printf (retval,
451                             "        <%s:%s/>\n",
452                             BOOKMARK_NAMESPACE_NAME,
453                             BOOKMARK_PRIVATE_ELEMENT);
454   
455   /* close metadata container */
456   g_string_append_printf (retval, "      </%s>\n", XBEL_METADATA_ELEMENT);
457                            
458   return g_string_free (retval, FALSE);
459 }
460
461 /******************************************************
462  * BookmarkItem                                       *
463  *                                                    *
464  * Storage for a single bookmark item inside the list *
465  ******************************************************/
466 static BookmarkItem *
467 bookmark_item_new (const gchar *uri)
468 {
469   BookmarkItem *item;
470  
471   g_assert (uri != NULL);
472   
473   item = g_slice_new (BookmarkItem);
474   item->uri = g_strdup (uri);
475   
476   item->title = NULL;
477   item->description = NULL;
478   
479   item->added = (time_t) -1;
480   item->modified = (time_t) -1;
481   item->visited = (time_t) -1;
482   
483   item->metadata = NULL;
484   
485   return item;
486 }
487
488 static void
489 bookmark_item_free (BookmarkItem *item)
490 {
491   if (!item)
492     return;
493
494   if (item->uri)
495     g_free (item->uri);
496   
497   if (item->title)
498     g_free (item->title);
499   
500   if (item->description)
501     g_free (item->description);
502   
503   if (item->metadata)
504     bookmark_metadata_free (item->metadata);
505   
506   g_slice_free (BookmarkItem, item);
507 }
508
509 static gchar *
510 bookmark_item_dump (BookmarkItem *item)
511 {
512   GString *retval;
513   gchar *added, *visited, *modified;
514   gchar *escaped_uri;
515  
516   /* at this point, we must have at least a registered application; if we don't
517    * we don't screw up the bookmark file, and just skip this item
518    */
519   if (!item->metadata || !item->metadata->applications)
520     {
521       g_warning ("Item for URI '%s' has no registered applications: skipping.\n", item->uri);
522       return NULL;
523     }
524   
525   retval = g_string_new (NULL);
526   
527   added = timestamp_to_iso8601 (item->added);
528   modified = timestamp_to_iso8601 (item->modified);
529   visited = timestamp_to_iso8601 (item->visited);
530
531   escaped_uri = g_markup_escape_text (item->uri, -1);
532   
533   g_string_append_printf (retval,
534                           "  <%s %s=\"%s\" %s=\"%s\" %s=\"%s\" %s=\"%s\">\n",
535                           XBEL_BOOKMARK_ELEMENT,
536                           XBEL_HREF_ATTRIBUTE, escaped_uri,
537                           XBEL_ADDED_ATTRIBUTE, added,
538                           XBEL_MODIFIED_ATTRIBUTE, modified,
539                           XBEL_VISITED_ATTRIBUTE, visited);
540   g_free (escaped_uri);
541   g_free (visited);
542   g_free (modified);
543   g_free (added);
544   
545   if (item->title)
546     {
547       gchar *escaped_title;
548       
549       escaped_title = g_markup_escape_text (item->title, -1);
550       g_string_append_printf (retval,
551                               "    <%s>%s</%s>\n",
552                               XBEL_TITLE_ELEMENT,
553                               escaped_title,
554                               XBEL_TITLE_ELEMENT);
555       g_free (escaped_title);
556     }
557   
558   if (item->description)
559     {
560       gchar *escaped_desc;
561       
562       escaped_desc = g_markup_escape_text (item->description, -1);
563       g_string_append_printf (retval,
564                               "    <%s>%s</%s>\n",
565                               XBEL_DESC_ELEMENT,
566                               escaped_desc,
567                               XBEL_DESC_ELEMENT);
568       g_free (escaped_desc);
569     }
570   
571   if (item->metadata)
572     {
573       gchar *metadata;
574       
575       /* open info container */
576       g_string_append_printf (retval, "    <%s>\n", XBEL_INFO_ELEMENT);
577       
578       metadata = bookmark_metadata_dump (item->metadata);
579       if (metadata)
580         {
581           retval = g_string_append (retval, metadata);
582
583           g_free (metadata);
584         }
585       
586       /* close info container */
587       g_string_append_printf (retval, "    </%s>\n", XBEL_INFO_ELEMENT);
588     }
589   
590   g_string_append_printf (retval, "  </%s>\n", XBEL_BOOKMARK_ELEMENT);
591   
592   return g_string_free (retval, FALSE);
593 }
594
595 static BookmarkAppInfo *
596 bookmark_item_lookup_app_info (BookmarkItem *item,
597                                const gchar  *app_name)
598 {
599   g_assert (item != NULL && app_name != NULL);
600
601   if (!item->metadata)
602     return NULL;
603   
604   return g_hash_table_lookup (item->metadata->apps_by_name, app_name);
605 }
606
607 /*************************
608  *    GBookmarkFile    *
609  *************************/
610  
611 static void
612 g_bookmark_file_init (GBookmarkFile *bookmark)
613 {
614   bookmark->title = NULL;
615   bookmark->description = NULL;
616   
617   bookmark->items = NULL;
618   bookmark->items_by_uri = g_hash_table_new_full (g_str_hash,
619                                                   g_str_equal,
620                                                   NULL,
621                                                   NULL);
622 }
623
624 static void
625 g_bookmark_file_clear (GBookmarkFile *bookmark)
626 {
627   g_free (bookmark->title);
628   g_free (bookmark->description);
629
630   if (bookmark->items)
631     {
632       g_list_foreach (bookmark->items,
633                       (GFunc) bookmark_item_free,
634                       NULL);
635       g_list_free (bookmark->items);
636       
637       bookmark->items = NULL;
638     }
639   
640   if (bookmark->items_by_uri)
641     {
642       g_hash_table_destroy (bookmark->items_by_uri);
643       
644       bookmark->items_by_uri = NULL;
645     }
646 }
647
648 struct _ParseData
649 {
650   gint state;
651   
652   GHashTable *namespaces;
653   
654   GBookmarkFile *bookmark_file;
655   BookmarkItem *current_item;
656 };
657
658 static ParseData *
659 parse_data_new (void)
660 {
661   ParseData *retval;
662   
663   retval = g_new (ParseData, 1);
664   
665   retval->state = STATE_STARTED;
666   retval->namespaces = g_hash_table_new_full (g_str_hash, g_str_equal,
667                                               (GDestroyNotify) g_free,
668                                               (GDestroyNotify) g_free);
669   retval->bookmark_file = NULL;
670   retval->current_item = NULL;
671   
672   return retval;
673 }
674
675 static void
676 parse_data_free (ParseData *parse_data)
677 {
678   g_hash_table_destroy (parse_data->namespaces);
679   
680   g_free (parse_data);
681 }
682
683 #define IS_ATTRIBUTE(s,a)       ((0 == strcmp ((s), (a))))
684
685 static void
686 parse_bookmark_element (GMarkupParseContext  *context,
687                         ParseData            *parse_data,
688                         const gchar         **attribute_names,
689                         const gchar         **attribute_values,
690                         GError              **error)
691 {
692   const gchar *uri, *added, *modified, *visited;
693   const gchar *attr;
694   gint i;
695   BookmarkItem *item;
696   GError *add_error;
697  
698   g_assert ((parse_data != NULL) && (parse_data->state == STATE_BOOKMARK));
699   
700   i = 0;
701   uri = added = modified = visited = NULL;
702   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
703     {
704       if (IS_ATTRIBUTE (attr, XBEL_HREF_ATTRIBUTE))
705         uri = attribute_values[i];
706       else if (IS_ATTRIBUTE (attr, XBEL_ADDED_ATTRIBUTE))
707         added = attribute_values[i];
708       else if (IS_ATTRIBUTE (attr, XBEL_MODIFIED_ATTRIBUTE))
709         modified = attribute_values[i];
710       else if (IS_ATTRIBUTE (attr, XBEL_VISITED_ATTRIBUTE))
711         visited = attribute_values[i];
712       else
713         {
714           g_set_error (error, G_MARKUP_ERROR,
715                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
716                        _("Unexpected attribute '%s' for element '%s'"),
717                        attr,
718                        XBEL_BOOKMARK_ELEMENT);
719           return;
720         }
721     }
722   
723   if (!uri)
724     {
725       g_set_error (error, G_MARKUP_ERROR,
726                    G_MARKUP_ERROR_INVALID_CONTENT,
727                    _("Attribute '%s' of element '%s' not found"),
728                    XBEL_HREF_ATTRIBUTE,
729                    XBEL_BOOKMARK_ELEMENT);
730       return;
731     }
732   
733   g_assert (parse_data->current_item == NULL);
734   
735   item = bookmark_item_new (uri);
736   
737   if (added)
738     item->added = timestamp_from_iso8601 (added);
739   
740   if (modified)
741     item->modified = timestamp_from_iso8601 (modified);
742   
743   if (visited)
744     item->visited = timestamp_from_iso8601 (visited);
745
746   add_error = NULL;
747   g_bookmark_file_add_item (parse_data->bookmark_file,
748                               item,
749                               &add_error);
750   if (add_error)
751     {
752       bookmark_item_free (item);
753       
754       g_propagate_error (error, add_error);
755       
756       return;
757     }                         
758   
759   parse_data->current_item = item;
760 }
761
762 static void
763 parse_application_element (GMarkupParseContext  *context,
764                            ParseData            *parse_data,
765                            const gchar         **attribute_names,
766                            const gchar         **attribute_values,
767                            GError              **error)
768 {
769   const gchar *name, *exec, *count, *stamp;
770   const gchar *attr;
771   gint i;
772   BookmarkItem *item;
773   BookmarkAppInfo *ai;
774   
775   g_assert ((parse_data != NULL) && (parse_data->state == STATE_APPLICATION));
776
777   i = 0;
778   name = exec = count = stamp = NULL;
779   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
780     {
781       if (IS_ATTRIBUTE (attr, BOOKMARK_NAME_ATTRIBUTE))
782         name = attribute_values[i];
783       else if (IS_ATTRIBUTE (attr, BOOKMARK_EXEC_ATTRIBUTE))
784         exec = attribute_values[i];
785       else if (IS_ATTRIBUTE (attr, BOOKMARK_COUNT_ATTRIBUTE))
786         count = attribute_values[i];
787       else if (IS_ATTRIBUTE (attr, BOOKMARK_TIMESTAMP_ATTRIBUTE))
788         stamp = attribute_values[i];
789       else
790         {
791           g_set_error (error, G_MARKUP_ERROR,
792                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
793                        _("Unexpected attribute '%s' for element '%s'"),
794                        attr,
795                        BOOKMARK_APPLICATION_ELEMENT);
796           return;
797         }        
798     }
799
800   if (!name)
801     {
802       g_set_error (error, G_MARKUP_ERROR,
803                    G_MARKUP_ERROR_INVALID_CONTENT,
804                    _("Attribute '%s' of element '%s' not found"),
805                    BOOKMARK_NAME_ATTRIBUTE,
806                    BOOKMARK_APPLICATION_ELEMENT);
807       return;
808     }
809   
810   if (!exec)
811     {
812       g_set_error (error, G_MARKUP_ERROR,
813                    G_MARKUP_ERROR_INVALID_CONTENT,
814                    _("Attribute '%s' of element '%s' not found"),
815                    BOOKMARK_EXEC_ATTRIBUTE,
816                    BOOKMARK_APPLICATION_ELEMENT);
817       return;
818     }
819
820   g_assert (parse_data->current_item != NULL);  
821   item = parse_data->current_item;
822     
823   ai = bookmark_item_lookup_app_info (item, name);
824   if (!ai)
825     {
826       ai = bookmark_app_info_new (name);
827       
828       if (!item->metadata)
829         item->metadata = bookmark_metadata_new ();
830       
831       item->metadata->applications = g_list_prepend (item->metadata->applications, ai);
832       g_hash_table_replace (item->metadata->apps_by_name, ai->name, ai);
833     }
834       
835   ai->exec = g_strdup (exec);
836   
837   if (count)
838     ai->count = atoi (count);
839   else
840     ai->count = 1;
841   
842   if (stamp)
843     ai->stamp = (time_t) atol (stamp);
844   else
845     ai->stamp = time (NULL);
846 }
847
848 static void
849 parse_mime_type_element (GMarkupParseContext  *context,
850                          ParseData            *parse_data,
851                          const gchar         **attribute_names,
852                          const gchar         **attribute_values,
853                          GError              **error)
854 {
855   const gchar *type;
856   const gchar *attr;
857   gint i;
858   BookmarkItem *item;
859   
860   g_assert ((parse_data != NULL) && (parse_data->state == STATE_MIME));
861   
862   i = 0;
863   type = NULL;
864   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
865     {
866       if (IS_ATTRIBUTE (attr, MIME_TYPE_ATTRIBUTE))
867         type = attribute_values[i];
868       else
869         {
870           g_set_error (error, G_MARKUP_ERROR,
871                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
872                        _("Unexpected attribute '%s' for element '%s'"),
873                        attr,
874                        MIME_TYPE_ELEMENT);
875           return;
876         }        
877     }
878
879   if (!type)
880     type = "application/octet-stream";
881
882   g_assert (parse_data->current_item != NULL);  
883   item = parse_data->current_item;
884     
885   if (!item->metadata)
886     item->metadata = bookmark_metadata_new ();
887   
888   item->metadata->mime_type = g_strdup (type);
889 }
890
891 static void
892 parse_icon_element (GMarkupParseContext  *context,
893                     ParseData            *parse_data,
894                     const gchar         **attribute_names,
895                     const gchar         **attribute_values,
896                     GError              **error)
897 {
898   const gchar *href;
899   const gchar *type;
900   const gchar *attr;
901   gint i;
902   BookmarkItem *item;
903   
904   g_assert ((parse_data != NULL) && (parse_data->state == STATE_ICON));
905   
906   i = 0;
907   href = NULL;
908   type = NULL;
909   for (attr = attribute_names[i]; attr != NULL; attr = attribute_names[++i])
910     {
911       if (IS_ATTRIBUTE (attr, BOOKMARK_HREF_ATTRIBUTE))
912         href = attribute_values[i];
913       else if (IS_ATTRIBUTE (attr, BOOKMARK_TYPE_ATTRIBUTE))
914         type = attribute_values[i];
915       else
916         {
917           g_set_error (error, G_MARKUP_ERROR,
918                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
919                        _("Unexpected attribute '%s' for element '%s'"),
920                        attr,
921                        BOOKMARK_ICON_ELEMENT);
922           return;
923         }        
924     }
925
926   if (!href)
927     {
928       g_set_error (error, G_MARKUP_ERROR,
929                    G_MARKUP_ERROR_INVALID_CONTENT,
930                    _("Attribute '%s' of element '%s' not found"),
931                    BOOKMARK_HREF_ATTRIBUTE,
932                    BOOKMARK_ICON_ELEMENT);
933       return;
934     }
935
936   if (!type)
937     type = "application/octet-stream";
938
939   g_assert (parse_data->current_item != NULL);  
940   item = parse_data->current_item;
941     
942   if (!item->metadata)
943     item->metadata = bookmark_metadata_new ();
944   
945   item->metadata->icon_href = g_strdup (href);
946   item->metadata->icon_mime = g_strdup (type);
947 }
948
949 /* scans through the attributes of an element for the "xmlns" pragma, and
950  * adds any resulting namespace declaration to a per-parser hashtable, using
951  * the namespace name as a key for the namespace URI; if no key was found,
952  * the namespace is considered as default, and stored under the "default" key.
953  *
954  * FIXME: this works on the assumption that the generator of the XBEL file
955  * is either this code or is smart enough to place the namespace declarations
956  * inside the main root node or inside the metadata node and does not redefine 
957  * a namespace inside an inner node; this does *not* conform to the
958  * XML-NS standard, although is a close approximation.  In order to make this
959  * conformant to the XML-NS specification we should use a per-element
960  * namespace table inside GMarkup and ask it to resolve the namespaces for us.
961  */
962 static void
963 map_namespace_to_name (ParseData    *parse_data,
964                        const gchar **attribute_names,
965                        const gchar **attribute_values)
966 {
967   const gchar *attr;
968   gint i;
969  
970   g_assert (parse_data != NULL);
971   
972   if (!attribute_names || !attribute_names[0])
973     return;
974   
975   i = 0;
976   for (attr = attribute_names[i]; attr; attr = attribute_names[++i])
977     {
978       if (g_str_has_prefix (attr, "xmlns"))
979         {
980           gchar *namespace_name, *namespace_uri;
981           gchar *p;
982           
983           p = g_utf8_strchr (attr, -1, ':');
984           if (p)
985             p = g_utf8_next_char (p);
986           else
987             p = "default";
988           
989           namespace_name = g_strdup (p);
990           namespace_uri = g_strdup (attribute_values[i]);
991           
992           g_hash_table_replace (parse_data->namespaces,
993                                 namespace_name,
994                                 namespace_uri);
995         }
996      }
997 }
998
999 /* checks whether @element_full is equal to @element.
1000  *
1001  * if @namespace is set, it tries to resolve the namespace to a known URI,
1002  * and if found is prepended to the element name, from which is separated
1003  * using the character specified in the @sep parameter.
1004  */
1005 static gboolean
1006 is_element_full (ParseData   *parse_data,
1007                  const gchar *element_full,
1008                  const gchar *namespace,
1009                  const gchar *element,
1010                  const gchar  sep)
1011 {
1012   gchar *ns_uri, *ns_name, *s, *resolved;
1013   const gchar *p, *element_name;
1014   gboolean retval;
1015  
1016   g_assert (parse_data != NULL);
1017   g_assert (element_full != NULL);
1018   
1019   if (!element)
1020     return FALSE;
1021     
1022   /* no namespace requested: dumb element compare */
1023   if (!namespace)
1024     return (0 == strcmp (element_full, element));
1025   
1026   /* search for namespace separator; if none found, assume we are under the
1027    * default namespace, and set ns_name to our "default" marker; if no default
1028    * namespace has been set, just do a plain comparison between @full_element
1029    * and @element.
1030    */
1031   p = strchr (element_full, ':');
1032   if (p)
1033     {
1034       ns_name = g_strndup (element_full, p - element_full);
1035       element_name = g_utf8_next_char (p);
1036     }
1037   else
1038     {
1039       ns_name = g_strdup ("default");
1040       element_name = element_full;
1041     }
1042   
1043   ns_uri = g_hash_table_lookup (parse_data->namespaces, ns_name);  
1044   if (!ns_uri)
1045     {
1046       /* no default namespace found */
1047       g_free (ns_name);
1048       
1049       return (0 == strcmp (element_full, element));
1050     }
1051   
1052   resolved = g_strdup_printf ("%s%c%s", ns_uri, sep, element_name);
1053   s = g_strdup_printf ("%s%c%s", namespace, sep, element);
1054   retval = (0 == strcmp (resolved, s));
1055   
1056   g_free (ns_name);
1057   g_free (resolved);
1058   g_free (s);
1059   
1060   return retval;
1061 }
1062
1063 #define IS_ELEMENT(p,s,e)       (is_element_full ((p), (s), NULL, (e), '\0'))
1064 #define IS_ELEMENT_NS(p,s,n,e)  (is_element_full ((p), (s), (n), (e), '|'))
1065
1066 static void
1067 start_element_raw_cb (GMarkupParseContext *context,
1068                       const gchar         *element_name,
1069                       const gchar        **attribute_names,
1070                       const gchar        **attribute_values,
1071                       gpointer             user_data,
1072                       GError             **error)
1073 {
1074   ParseData *parse_data = (ParseData *) user_data;
1075
1076   /* we must check for namespace declarations first
1077    * 
1078    * XXX - we could speed up things by checking for namespace declarations
1079    * only on the root node, where they usually are; this would probably break
1080    * on streams not produced by us or by "smart" generators
1081    */
1082   map_namespace_to_name (parse_data, attribute_names, attribute_values);
1083   
1084   switch (parse_data->state)
1085     {
1086     case STATE_STARTED:
1087       if (IS_ELEMENT (parse_data, element_name, XBEL_ROOT_ELEMENT))
1088         {
1089           const gchar *attr;
1090           gint i;
1091           
1092           i = 0;
1093           for (attr = attribute_names[i]; attr; attr = attribute_names[++i])
1094             {
1095               if ((IS_ATTRIBUTE (attr, XBEL_VERSION_ATTRIBUTE)) &&
1096                   (0 == strcmp (attribute_values[i], XBEL_VERSION)))
1097                 parse_data->state = STATE_ROOT;
1098             }
1099         }
1100       else
1101         g_set_error (error, G_MARKUP_ERROR,
1102                      G_MARKUP_ERROR_INVALID_CONTENT,
1103                      _("Unexpected tag '%s', tag '%s' expected"),
1104                      element_name, XBEL_ROOT_ELEMENT);
1105       break;
1106     case STATE_ROOT:
1107       if (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT))
1108         parse_data->state = STATE_TITLE;
1109       else if (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT))
1110         parse_data->state = STATE_DESC;
1111       else if (IS_ELEMENT (parse_data, element_name, XBEL_BOOKMARK_ELEMENT))
1112         {
1113           GError *inner_error = NULL;
1114           
1115           parse_data->state = STATE_BOOKMARK;
1116           
1117           parse_bookmark_element (context,
1118                                   parse_data,
1119                                   attribute_names,
1120                                   attribute_values,
1121                                   &inner_error);
1122           if (inner_error)
1123             g_propagate_error (error, inner_error);
1124         }
1125       else
1126         g_set_error (error, G_MARKUP_ERROR,
1127                      G_MARKUP_ERROR_INVALID_CONTENT,
1128                      _("Unexpected tag '%s' inside '%s'"),
1129                      element_name,
1130                      XBEL_ROOT_ELEMENT);
1131       break;
1132     case STATE_BOOKMARK:
1133       if (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT))
1134         parse_data->state = STATE_TITLE;
1135       else if (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT))
1136         parse_data->state = STATE_DESC;
1137       else if (IS_ELEMENT (parse_data, element_name, XBEL_INFO_ELEMENT))
1138         parse_data->state = STATE_INFO;
1139       else
1140         g_set_error (error, G_MARKUP_ERROR,
1141                      G_MARKUP_ERROR_INVALID_CONTENT,
1142                      _("Unexpected tag '%s' inside '%s'"),
1143                      element_name,
1144                      XBEL_BOOKMARK_ELEMENT);
1145       break;
1146     case STATE_INFO:
1147       if (IS_ELEMENT (parse_data, element_name, XBEL_METADATA_ELEMENT))
1148         {
1149           const gchar *attr;
1150           gint i;
1151           
1152           i = 0;
1153           for (attr = attribute_names[i]; attr; attr = attribute_names[++i])
1154             {
1155               if ((IS_ATTRIBUTE (attr, XBEL_OWNER_ATTRIBUTE)) &&
1156                   (0 == strcmp (attribute_values[i], BOOKMARK_METADATA_OWNER)))
1157                 {
1158                   parse_data->state = STATE_METADATA;
1159                   
1160                   if (!parse_data->current_item->metadata)
1161                     parse_data->current_item->metadata = bookmark_metadata_new ();
1162                 }
1163             }
1164         }
1165       else
1166         g_set_error (error, G_MARKUP_ERROR,
1167                      G_MARKUP_ERROR_INVALID_CONTENT,
1168                      _("Unexpected tag '%s', tag '%s' expected"),
1169                      element_name,
1170                      XBEL_METADATA_ELEMENT);
1171       break;
1172     case STATE_METADATA:
1173       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATIONS_ELEMENT))
1174         parse_data->state = STATE_APPLICATIONS;
1175       else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUPS_ELEMENT))
1176         parse_data->state = STATE_GROUPS;
1177       else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_PRIVATE_ELEMENT))
1178         parse_data->current_item->metadata->is_private = TRUE;
1179       else if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT))
1180         {
1181           GError *inner_error = NULL;
1182           
1183           parse_data->state = STATE_ICON;
1184           
1185           parse_icon_element (context,
1186                               parse_data,
1187                               attribute_names,
1188                               attribute_values,
1189                               &inner_error);
1190           if (inner_error)
1191             g_propagate_error (error, inner_error);
1192         }
1193       else if (IS_ELEMENT_NS (parse_data, element_name, MIME_NAMESPACE_URI, MIME_TYPE_ELEMENT))
1194         {
1195           GError *inner_error = NULL;
1196           
1197           parse_data->state = STATE_MIME;
1198           
1199           parse_mime_type_element (context,
1200                                    parse_data,
1201                                    attribute_names,
1202                                    attribute_values,
1203                                    &inner_error);
1204           if (inner_error)
1205             g_propagate_error (error, inner_error);
1206         }
1207       else
1208         g_set_error (error, G_MARKUP_ERROR,
1209                      G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1210                      _("Unexpected tag '%s' inside '%s'"),
1211                      element_name,
1212                      XBEL_METADATA_ELEMENT);
1213       break;
1214     case STATE_APPLICATIONS:
1215       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATION_ELEMENT))
1216         {
1217           GError *inner_error = NULL;
1218           
1219           parse_data->state = STATE_APPLICATION;
1220           
1221           parse_application_element (context,
1222                                      parse_data,
1223                                      attribute_names,
1224                                      attribute_values,
1225                                      &inner_error);
1226           if (inner_error)
1227             g_propagate_error (error, inner_error);
1228         }
1229       else
1230         g_set_error (error, G_MARKUP_ERROR,
1231                      G_MARKUP_ERROR_INVALID_CONTENT,
1232                      _("Unexpected tag '%s', tag '%s' expected"),
1233                      element_name,
1234                      BOOKMARK_APPLICATION_ELEMENT);
1235       break;
1236     case STATE_GROUPS:
1237       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUP_ELEMENT))
1238         parse_data->state = STATE_GROUP;
1239       else
1240         g_set_error (error, G_MARKUP_ERROR,
1241                      G_MARKUP_ERROR_INVALID_CONTENT,
1242                      _("Unexpected tag '%s', tag '%s' expected"),
1243                      element_name,
1244                      BOOKMARK_GROUP_ELEMENT);
1245       break;
1246     case STATE_ICON:
1247       if (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT))
1248         {
1249           GError *inner_error = NULL;
1250           
1251           parse_icon_element (context,
1252                               parse_data,
1253                               attribute_names,
1254                               attribute_values,
1255                               &inner_error);
1256           if (inner_error)
1257             g_propagate_error (error, inner_error);
1258         }
1259       else
1260         g_set_error (error, G_MARKUP_ERROR,
1261                      G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1262                      _("Unexpected tag '%s' inside '%s'"),
1263                      element_name,
1264                      XBEL_METADATA_ELEMENT);
1265       break;
1266     default:
1267       g_assert_not_reached ();
1268       break;
1269     }
1270 }
1271
1272 static void
1273 end_element_raw_cb (GMarkupParseContext *context,
1274                     const gchar         *element_name,
1275                     gpointer             user_data,
1276                     GError             **error)
1277 {
1278   ParseData *parse_data = (ParseData *) user_data;
1279   
1280   if (IS_ELEMENT (parse_data, element_name, XBEL_ROOT_ELEMENT))
1281     parse_data->state = STATE_FINISHED;
1282   else if (IS_ELEMENT (parse_data, element_name, XBEL_BOOKMARK_ELEMENT))
1283     {
1284       parse_data->current_item = NULL;
1285       
1286       parse_data->state = STATE_ROOT;
1287     }
1288   else if ((IS_ELEMENT (parse_data, element_name, XBEL_INFO_ELEMENT)) ||
1289            (IS_ELEMENT (parse_data, element_name, XBEL_TITLE_ELEMENT)) ||
1290            (IS_ELEMENT (parse_data, element_name, XBEL_DESC_ELEMENT)))
1291     {
1292       if (parse_data->current_item)
1293         parse_data->state = STATE_BOOKMARK;
1294       else
1295         parse_data->state = STATE_ROOT;
1296     }
1297   else if (IS_ELEMENT (parse_data, element_name, XBEL_METADATA_ELEMENT))
1298     parse_data->state = STATE_INFO;
1299   else if (IS_ELEMENT_NS (parse_data, element_name,
1300                           BOOKMARK_NAMESPACE_URI,
1301                           BOOKMARK_APPLICATION_ELEMENT))
1302     parse_data->state = STATE_APPLICATIONS;
1303   else if (IS_ELEMENT_NS (parse_data, element_name,
1304                           BOOKMARK_NAMESPACE_URI,
1305                           BOOKMARK_GROUP_ELEMENT))
1306     parse_data->state = STATE_GROUPS;
1307   else if ((IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_APPLICATIONS_ELEMENT)) ||
1308            (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_GROUPS_ELEMENT)) ||
1309            (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_PRIVATE_ELEMENT)) ||
1310            (IS_ELEMENT_NS (parse_data, element_name, BOOKMARK_NAMESPACE_URI, BOOKMARK_ICON_ELEMENT)) ||
1311            (IS_ELEMENT_NS (parse_data, element_name, MIME_NAMESPACE_URI, MIME_TYPE_ELEMENT)))
1312     parse_data->state = STATE_METADATA;
1313 }
1314
1315 static void
1316 text_raw_cb (GMarkupParseContext *context,
1317              const gchar         *text,
1318              gsize                length,
1319              gpointer             user_data,
1320              GError             **error)
1321 {
1322   ParseData *parse_data = (ParseData *) user_data;
1323   gchar *payload;
1324   
1325   payload = g_strndup (text, length);
1326   
1327   switch (parse_data->state)
1328     {
1329     case STATE_TITLE:
1330       if (parse_data->current_item)
1331         {
1332           g_free (parse_data->current_item->title);
1333           parse_data->current_item->title = g_strdup (payload);
1334         }
1335       else
1336         {
1337           g_free (parse_data->bookmark_file->title);
1338           parse_data->bookmark_file->title = g_strdup (payload);
1339         }
1340       break;
1341     case STATE_DESC:
1342       if (parse_data->current_item)
1343         {
1344           g_free (parse_data->current_item->description);
1345           parse_data->current_item->description = g_strdup (payload);
1346         }
1347       else
1348         {
1349           g_free (parse_data->bookmark_file->description);
1350           parse_data->bookmark_file->description = g_strdup (payload);
1351         }
1352       break;
1353     case STATE_GROUP:
1354       {
1355       GList *groups;
1356       
1357       g_assert (parse_data->current_item != NULL);
1358       
1359       if (!parse_data->current_item->metadata)
1360         parse_data->current_item->metadata = bookmark_metadata_new ();
1361       
1362       groups = parse_data->current_item->metadata->groups;
1363       parse_data->current_item->metadata->groups = g_list_prepend (groups, g_strdup (payload));
1364       }
1365       break;
1366     case STATE_ROOT:
1367     case STATE_BOOKMARK:
1368     case STATE_INFO:
1369     case STATE_METADATA:
1370     case STATE_APPLICATIONS:
1371     case STATE_APPLICATION:
1372     case STATE_GROUPS:
1373     case STATE_MIME:
1374     case STATE_ICON:
1375       break;
1376     default:
1377       g_assert_not_reached ();
1378       break;
1379     }
1380   
1381   g_free (payload);
1382 }
1383
1384 static const GMarkupParser markup_parser =
1385 {
1386   start_element_raw_cb, /* start_element */
1387   end_element_raw_cb,   /* end_element */
1388   text_raw_cb,          /* text */
1389   NULL,                 /* passthrough */
1390   NULL
1391 };
1392
1393 static gboolean
1394 g_bookmark_file_parse (GBookmarkFile  *bookmark,
1395                          const gchar      *buffer,
1396                          gsize             length,
1397                          GError          **error)
1398 {
1399   GMarkupParseContext *context;
1400   ParseData *parse_data;
1401   GError *parse_error, *end_error;
1402   gboolean retval;
1403   
1404   g_assert (bookmark != NULL);
1405
1406   if (!buffer)
1407     return FALSE;
1408   
1409   if (length == -1)
1410     length = strlen (buffer);
1411
1412   parse_data = parse_data_new ();
1413   parse_data->bookmark_file = bookmark;
1414   
1415   context = g_markup_parse_context_new (&markup_parser,
1416                                         0,
1417                                         parse_data,
1418                                         (GDestroyNotify) parse_data_free);
1419   
1420   parse_error = NULL;
1421   retval = g_markup_parse_context_parse (context,
1422                                          buffer,
1423                                          length,
1424                                          &parse_error);
1425   if (!retval)
1426     {
1427       g_propagate_error (error, parse_error);
1428       
1429       return FALSE;
1430     }
1431   
1432   end_error = NULL;
1433   retval = g_markup_parse_context_end_parse (context, &end_error);
1434   if (!retval)
1435     {
1436       g_propagate_error (error, end_error);
1437       
1438       return FALSE;
1439     }
1440   
1441   g_markup_parse_context_free (context);
1442   
1443   return TRUE;
1444 }
1445
1446 static gchar *
1447 g_bookmark_file_dump (GBookmarkFile  *bookmark,
1448                       gsize          *length,
1449                       GError        **error)
1450 {
1451   GString *retval;
1452   GList *l;
1453   
1454   retval = g_string_new (NULL);
1455   
1456   g_string_append_printf (retval,
1457                           "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1458 #if 0
1459                           /* XXX - do we really need the doctype? */
1460                           "<!DOCTYPE %s\n"
1461                           "  PUBLIC \"%s\"\n"
1462                           "         \"%s\">\n"
1463 #endif
1464                           "<%s %s=\"%s\"\n"
1465                           "      xmlns:%s=\"%s\"\n"
1466                           "      xmlns:%s=\"%s\"\n>",
1467 #if 0
1468                           /* XXX - do we really need the doctype? */
1469                           XBEL_DTD_NICK,
1470                           XBEL_DTD_SYSTEM, XBEL_DTD_URI,
1471 #endif
1472                           XBEL_ROOT_ELEMENT,
1473                           XBEL_VERSION_ATTRIBUTE, XBEL_VERSION,
1474                           BOOKMARK_NAMESPACE_NAME, BOOKMARK_NAMESPACE_URI,
1475                           MIME_NAMESPACE_NAME, MIME_NAMESPACE_URI);
1476   
1477   if (bookmark->title)
1478     {
1479       gchar *escaped_title;
1480       
1481       escaped_title = g_markup_escape_text (bookmark->title, -1);
1482       
1483       g_string_append_printf (retval, "  <%s>%s</%s>\n",
1484                               XBEL_TITLE_ELEMENT,
1485                               escaped_title,
1486                               XBEL_TITLE_ELEMENT);
1487       
1488       g_free (escaped_title);
1489     }
1490   
1491   if (bookmark->description)
1492     {
1493       gchar *escaped_desc;
1494       
1495       escaped_desc = g_markup_escape_text (bookmark->description, -1);
1496       
1497       g_string_append_printf (retval, "  <%s>%s</%s>\n",
1498                               XBEL_DESC_ELEMENT,
1499                               escaped_desc,
1500                               XBEL_DESC_ELEMENT);
1501       
1502       g_free (escaped_desc);
1503     }
1504   
1505   if (!bookmark->items)
1506     goto out;
1507   else
1508     retval = g_string_append (retval, "\n");
1509   
1510   for (l = g_list_last (bookmark->items);
1511        l != NULL;
1512        l = l->prev)
1513     {
1514       BookmarkItem *item = (BookmarkItem *) l->data;
1515       gchar *item_dump;
1516       
1517       item_dump = bookmark_item_dump (item);
1518       if (!item_dump)
1519         continue;
1520       
1521       retval = g_string_append (retval, item_dump);
1522       
1523       g_free (item_dump);      
1524     }
1525
1526 out:
1527   g_string_append_printf (retval, "</%s>", XBEL_ROOT_ELEMENT);
1528   
1529   if (length)
1530     *length = retval->len;
1531   
1532   return g_string_free (retval, FALSE);
1533 }
1534
1535 /**************
1536  *    Misc    *
1537  **************/
1538  
1539 /* converts a Unix timestamp in a ISO 8601 compliant string; you
1540  * should free the returned string.
1541  */
1542 static gchar *
1543 timestamp_to_iso8601 (time_t timestamp)
1544 {
1545   GTimeVal stamp;
1546
1547   if (timestamp == (time_t) -1)
1548     g_get_current_time (&stamp);
1549   else
1550     {
1551       stamp.tv_sec = timestamp;
1552       stamp.tv_usec = 0;
1553     }
1554
1555   return g_time_val_to_iso8601 (&stamp);
1556 }
1557
1558 static time_t
1559 timestamp_from_iso8601 (const gchar *iso_date)
1560 {
1561   GTimeVal stamp;
1562
1563   if (!g_time_val_from_iso8601 (iso_date, &stamp))
1564     return (time_t) -1;
1565
1566   return (time_t) stamp.tv_sec;
1567 }
1568
1569
1570
1571 GQuark
1572 g_bookmark_file_error_quark (void)
1573 {
1574   return g_quark_from_static_string ("egg-bookmark-file-error-quark");
1575 }
1576
1577
1578
1579 /********************
1580  *    Public API    *
1581  ********************/
1582
1583 /**
1584  * g_bookmark_file_new:
1585  *
1586  * Creates a new empty #GBookmarkFile object.
1587  *
1588  * Use g_bookmark_file_load_from_file(), g_bookmark_file_load_from_data()
1589  * or g_bookmark_file_load_from_data_dirs() to read an existing bookmark
1590  * file.
1591  *
1592  * Return value: an empty #GBookmarkFile
1593  *
1594  * Since: 2.12
1595  */
1596 GBookmarkFile *
1597 g_bookmark_file_new (void)
1598 {
1599   GBookmarkFile *bookmark;
1600   
1601   bookmark = g_new (GBookmarkFile, 1);
1602   
1603   g_bookmark_file_init (bookmark);
1604   
1605   return bookmark;
1606 }
1607
1608 /**
1609  * g_bookmark_file_free:
1610  * @bookmark: a #GBookmarkFile
1611  *
1612  * Frees a #GBookmarkFile.
1613  *
1614  * Since: 2.12
1615  */
1616 void
1617 g_bookmark_file_free (GBookmarkFile *bookmark)
1618 {
1619   if (!bookmark)
1620     return;
1621   
1622   g_bookmark_file_clear (bookmark);
1623   
1624   g_free (bookmark);  
1625 }
1626
1627 /**
1628  * g_bookmark_file_load_from_data:
1629  * @bookmark: an empty #GBookmarkFile struct
1630  * @data: desktop bookmarks loaded in memory
1631  * @length: the length of @data in bytes
1632  * @error: return location for a #GError, or %NULL
1633  *
1634  * Loads a bookmark file from memory into an empty #GBookmarkFile
1635  * structure.  If the object cannot be created then @error is set to a
1636  * #GBookmarkFileError.
1637  *
1638  * Return value: %TRUE if a desktop bookmark could be loaded.
1639  *
1640  * Since: 2.12
1641  */
1642 gboolean
1643 g_bookmark_file_load_from_data (GBookmarkFile  *bookmark,
1644                                 const gchar    *data,
1645                                 gsize           length,
1646                                 GError        **error)
1647 {
1648   GError *parse_error;
1649   gboolean retval;
1650   
1651   g_return_val_if_fail (bookmark != NULL, FALSE);
1652   g_return_val_if_fail (data != NULL, FALSE);
1653   g_return_val_if_fail (length != 0, FALSE);
1654   
1655   if (length == (gsize) -1)
1656     length = strlen (data);
1657
1658   if (bookmark->items)
1659     {
1660       g_bookmark_file_clear (bookmark);
1661       g_bookmark_file_init (bookmark);
1662     }
1663   
1664   parse_error = NULL;
1665   retval = g_bookmark_file_parse (bookmark, data, length, &parse_error);
1666   if (!retval)
1667     {
1668       g_propagate_error (error, parse_error);
1669       
1670       return FALSE;
1671     }
1672   
1673   return TRUE;
1674 }
1675
1676 /**
1677  * g_bookmark_file_load_from_file:
1678  * @bookmark: an empty #GBookmarkFile struct
1679  * @filename: the path of a filename to load, in the GLib file name encoding
1680  * @error: return location for a #GError, or %NULL
1681  *
1682  * Loads a desktop bookmark file into an empty #GBookmarkFile structure.
1683  * If the file could not be loaded then @error is set to either a #GFileError
1684  * or #GBookmarkFileError.
1685  *
1686  * Return value: %TRUE if a desktop bookmark file could be loaded
1687  *
1688  * Since: 2.12
1689  */
1690 gboolean
1691 g_bookmark_file_load_from_file (GBookmarkFile  *bookmark,
1692                                 const gchar    *filename,
1693                                 GError        **error)
1694 {
1695   gchar *buffer;
1696   gsize len;
1697   GError *read_error;
1698   gboolean retval;
1699         
1700   g_return_val_if_fail (bookmark != NULL, FALSE);
1701   g_return_val_if_fail (filename != NULL, FALSE);
1702
1703   read_error = NULL;
1704   g_file_get_contents (filename, &buffer, &len, &read_error);
1705   if (read_error)
1706     {
1707       g_propagate_error (error, read_error);
1708
1709       return FALSE;
1710     }
1711   
1712   read_error = NULL;
1713   retval = g_bookmark_file_load_from_data (bookmark,
1714                                            buffer,
1715                                            len,
1716                                            &read_error);
1717   if (read_error)
1718     {
1719       g_propagate_error (error, read_error);
1720
1721       g_free (buffer);
1722
1723       return FALSE;
1724     }
1725
1726   g_free (buffer);
1727
1728   return retval;
1729 }
1730
1731
1732 /* Iterates through all the directories in *dirs trying to
1733  * find file.  When it successfully locates file, returns a
1734  * string its absolute path.  It also leaves the unchecked
1735  * directories in *dirs.  You should free the returned string
1736  *
1737  * Adapted from gkeyfile.c
1738  */
1739 static gchar *
1740 find_file_in_data_dirs (const gchar   *file,
1741                         gchar       ***dirs,
1742                         GError       **error)
1743 {
1744   gchar **data_dirs, *data_dir, *path;
1745
1746   path = NULL;
1747
1748   if (dirs == NULL)
1749     return NULL;
1750
1751   data_dirs = *dirs;
1752   path = NULL;
1753   while (data_dirs && (data_dir = *data_dirs) && !path)
1754     {
1755       gchar *candidate_file, *sub_dir;
1756
1757       candidate_file = (gchar *) file;
1758       sub_dir = g_strdup ("");
1759       while (candidate_file != NULL && !path)
1760         {
1761           gchar *p;
1762
1763           path = g_build_filename (data_dir, sub_dir,
1764                                    candidate_file, NULL);
1765
1766           candidate_file = strchr (candidate_file, '-');
1767
1768           if (candidate_file == NULL)
1769             break;
1770
1771           candidate_file++;
1772
1773           g_free (sub_dir);
1774           sub_dir = g_strndup (file, candidate_file - file - 1);
1775
1776           for (p = sub_dir; *p != '\0'; p++)
1777             {
1778               if (*p == '-')
1779                 *p = G_DIR_SEPARATOR;
1780             }
1781         }
1782       g_free (sub_dir);
1783       data_dirs++;
1784     }
1785
1786   *dirs = data_dirs;
1787
1788   if (!path)
1789     {
1790       g_set_error (error, G_BOOKMARK_FILE_ERROR,
1791                    G_BOOKMARK_FILE_ERROR_FILE_NOT_FOUND,
1792                    _("No valid bookmark file was be found in data dirs"));
1793       
1794       return NULL;
1795     }
1796   
1797   return path;
1798 }
1799
1800
1801 /**
1802  * g_bookmark_file_load_from_data_dirs:
1803  * @bookmark: a #GBookmarkFile
1804  * @file: a relative path to a filename to open and parse
1805  * @full_path: return location for a string containing the full path
1806  *   of the file, or %NULL
1807  * @error: return location for a #GError, or %NULL
1808  *
1809  * This function looks for a desktop bookmark file named @file in the
1810  * paths returned from g_get_user_data_dir() and g_get_system_data_dirs(), 
1811  * loads the file into @bookmark and returns the file's full path in 
1812  * @full_path.  If the file could not be loaded then an %error is
1813  * set to either a #GFileError or #GBookmarkFileError.
1814  *
1815  * Return value: %TRUE if a key file could be loaded, %FALSE othewise
1816  *
1817  * Since: 2.12
1818  */
1819 gboolean
1820 g_bookmark_file_load_from_data_dirs (GBookmarkFile  *bookmark,
1821                                      const gchar    *file,
1822                                      gchar         **full_path,
1823                                      GError        **error)
1824 {
1825   GError *file_error = NULL;
1826   gchar **all_data_dirs, **data_dirs;
1827   const gchar *user_data_dir;
1828   const gchar * const * system_data_dirs;
1829   gsize i, j;
1830   gchar *output_path;
1831   gboolean found_file;
1832   
1833   g_return_val_if_fail (bookmark != NULL, FALSE);
1834   g_return_val_if_fail (!g_path_is_absolute (file), FALSE);
1835   
1836   user_data_dir = g_get_user_data_dir ();
1837   system_data_dirs = g_get_system_data_dirs ();
1838   all_data_dirs = g_new0 (gchar *, g_strv_length ((gchar **)system_data_dirs) + 2);
1839
1840   i = 0;
1841   all_data_dirs[i++] = g_strdup (user_data_dir);
1842
1843   j = 0;
1844   while (system_data_dirs[j] != NULL)
1845     all_data_dirs[i++] = g_strdup (system_data_dirs[j++]);
1846
1847   found_file = FALSE;
1848   data_dirs = all_data_dirs;
1849   output_path = NULL;
1850   while (*data_dirs != NULL && !found_file)
1851     {
1852       g_free (output_path);
1853
1854       output_path = find_file_in_data_dirs (file, &data_dirs, &file_error);
1855       
1856       if (file_error)
1857         {
1858           g_propagate_error (error, file_error);
1859           break;
1860         }
1861
1862       found_file = g_bookmark_file_load_from_file (bookmark,
1863                                                    output_path,
1864                                                    &file_error);
1865       if (file_error)
1866         {
1867           g_propagate_error (error, file_error);
1868           break;
1869         }
1870     }
1871
1872   if (found_file && full_path)
1873     *full_path = output_path;
1874   else 
1875     g_free (output_path);
1876
1877   g_strfreev (all_data_dirs);
1878
1879   return found_file;
1880 }
1881
1882
1883 /**
1884  * g_bookmark_file_to_data:
1885  * @bookmark: a #GBookmarkFile
1886  * @length: return location for the length of the returned string, or %NULL
1887  * @error: return location for a #GError, or %NULL
1888  *
1889  * This function outputs @bookmark as a string.
1890  *
1891  * Return value: a newly allocated string holding
1892  *   the contents of the #GBookmarkFile
1893  *
1894  * Since: 2.12
1895  */
1896 gchar *
1897 g_bookmark_file_to_data (GBookmarkFile  *bookmark,
1898                          gsize          *length,
1899                          GError        **error)
1900 {
1901   GError *write_error = NULL;
1902   gchar *retval;
1903   
1904   g_return_val_if_fail (bookmark != NULL, NULL);
1905   
1906   retval = g_bookmark_file_dump (bookmark, length, &write_error);
1907   if (write_error)
1908     {
1909       g_propagate_error (error, write_error);
1910       
1911       return NULL;
1912     }
1913       
1914   return retval;
1915 }
1916
1917 /**
1918  * g_bookmark_file_to_file:
1919  * @bookmark: a #GBookmarkFile
1920  * @filename: path of the output file
1921  * @error: return location for a #GError, or %NULL
1922  *
1923  * This function outputs @bookmark into a file.  The write process is
1924  * guaranteed to be atomic by using g_file_set_contents() internally.
1925  *
1926  * Return value: %TRUE if the file was successfully written.
1927  *
1928  * Since: 2.12
1929  */
1930 gboolean
1931 g_bookmark_file_to_file (GBookmarkFile  *bookmark,
1932                          const gchar    *filename,
1933                          GError        **error)
1934 {
1935   gchar *data;
1936   GError *data_error, *write_error;
1937   gsize len;
1938   gboolean retval;
1939
1940   g_return_val_if_fail (bookmark != NULL, FALSE);
1941   g_return_val_if_fail (filename != NULL, FALSE);
1942   
1943   data_error = NULL;
1944   data = g_bookmark_file_to_data (bookmark, &len, &data_error);
1945   if (data_error)
1946     {
1947       g_propagate_error (error, data_error);
1948       
1949       return FALSE;
1950     }
1951
1952   write_error = NULL;
1953   g_file_set_contents (filename, data, len, &write_error);
1954   if (write_error)
1955     {
1956       g_propagate_error (error, write_error);
1957       
1958       retval = FALSE;
1959     }
1960   else
1961     retval = TRUE;
1962
1963   g_free (data);
1964   
1965   return retval;
1966 }
1967
1968 static BookmarkItem *
1969 g_bookmark_file_lookup_item (GBookmarkFile *bookmark,
1970                              const gchar   *uri)
1971 {
1972   g_assert (bookmark != NULL && uri != NULL);
1973   
1974   return g_hash_table_lookup (bookmark->items_by_uri, uri);
1975 }
1976
1977 /* this function adds a new item to the list */
1978 static void
1979 g_bookmark_file_add_item (GBookmarkFile  *bookmark,
1980                           BookmarkItem   *item,
1981                           GError        **error)
1982 {
1983   g_assert (bookmark != NULL);
1984   g_assert (item != NULL);
1985
1986   /* this should never happen; and if it does, then we are
1987    * screwing up something big time.
1988    */
1989   if (G_UNLIKELY (g_bookmark_file_has_item (bookmark, item->uri)))
1990     {
1991       g_set_error (error, G_BOOKMARK_FILE_ERROR,
1992                    G_BOOKMARK_FILE_ERROR_INVALID_URI,
1993                    _("A bookmark for URI '%s' already exists"),
1994                    item->uri);
1995       return;
1996     }
1997   
1998   bookmark->items = g_list_prepend (bookmark->items, item);
1999   
2000   g_hash_table_replace (bookmark->items_by_uri,
2001                         item->uri,
2002                         item);
2003
2004   if (item->added == (time_t) -1)
2005     item->added = time (NULL);
2006   
2007   if (item->modified == (time_t) -1)
2008     item->modified = time (NULL);
2009 }
2010
2011 /**
2012  * g_bookmark_file_remove_item:
2013  * @bookmark: a #GBookmarkFile
2014  * @uri: a valid URI
2015  * @error: return location for a #GError, or %NULL
2016  *
2017  * Removes the bookmark for @uri from the bookmark file @bookmark.
2018  *
2019  * Since: 2.12
2020  */
2021 void
2022 g_bookmark_file_remove_item (GBookmarkFile  *bookmark,
2023                              const gchar    *uri,
2024                              GError        **error)
2025 {
2026   BookmarkItem *item;
2027   
2028   g_return_if_fail (bookmark != NULL);
2029   g_return_if_fail (uri != NULL);
2030   
2031   item = g_bookmark_file_lookup_item (bookmark, uri);
2032   
2033   if (!item)
2034     {
2035       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2036                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2037                    _("No bookmark found for URI '%s'"),
2038                    uri);
2039       return;    
2040     }
2041
2042   bookmark->items = g_list_remove (bookmark->items, item);
2043   g_hash_table_remove (bookmark->items_by_uri, item->uri);  
2044   
2045   bookmark_item_free (item);
2046 }
2047
2048 /**
2049  * g_bookmark_file_has_item:
2050  * @bookmark: a #GBookmarkFile
2051  * @uri: a valid URI
2052  *
2053  * Looks whether the desktop bookmark has an item with its URI set to @uri.
2054  *
2055  * Return value: %TRUE if @uri is inside @bookmark, %FALSE otherwise
2056  *
2057  * Since: 2.12
2058  */
2059 gboolean
2060 g_bookmark_file_has_item (GBookmarkFile *bookmark,
2061                           const gchar   *uri)
2062 {
2063   g_return_val_if_fail (bookmark != NULL, FALSE);
2064   g_return_val_if_fail (uri != NULL, FALSE);
2065   
2066   return (NULL != g_hash_table_lookup (bookmark->items_by_uri, uri));
2067 }
2068
2069 /**
2070  * g_bookmark_file_get_uris:
2071  * @bookmark: a #GBookmarkFile
2072  * @length: return location for the number of returned URIs, or %NULL
2073  *
2074  * Returns all URIs of the bookmarks in the bookmark file @bookmark.
2075  * The array of returned URIs will be %NULL-terminated, so @length may
2076  * optionally be %NULL.
2077  *
2078  * Return value: a newly allocated %NULL-terminated array of strings.
2079  *   Use g_strfreev() to free it.
2080  *
2081  * Since: 2.12
2082  */
2083 gchar **
2084 g_bookmark_file_get_uris (GBookmarkFile *bookmark,
2085                           gsize         *length)
2086 {
2087   GList *l;
2088   gchar **uris;
2089   gsize i, n_items;
2090   
2091   g_return_val_if_fail (bookmark != NULL, NULL);
2092   
2093   n_items = g_list_length (bookmark->items); 
2094   uris = g_new0 (gchar *, n_items + 1);
2095   
2096   for (l = g_list_last (bookmark->items), i = 0; l != NULL; l = l->prev)
2097     {
2098       BookmarkItem *item = (BookmarkItem *) l->data;
2099       
2100       g_assert (item != NULL);
2101       
2102       uris[i++] = g_strdup (item->uri);
2103     }
2104   uris[i] = NULL;
2105   
2106   if (length)
2107     *length = i;
2108   
2109   return uris;
2110 }
2111
2112 /**
2113  * g_bookmark_file_set_title:
2114  * @bookmark: a #GBookmarkFile
2115  * @uri: a valid URI or %NULL
2116  * @title: a UTF-8 encoded string
2117  *
2118  * Sets @title as the title of the bookmark for @uri inside the
2119  * bookmark file @bookmark.
2120  *
2121  * If @uri is %NULL, the title of @bookmark is set.
2122  *
2123  * If a bookmark for @uri cannot be found then it is created.
2124  *
2125  * Since: 2.12
2126  */
2127 void
2128 g_bookmark_file_set_title (GBookmarkFile *bookmark,
2129                            const gchar   *uri,
2130                            const gchar   *title)
2131 {
2132   g_return_if_fail (bookmark != NULL);
2133   
2134   if (!uri)
2135     {
2136       g_free (bookmark->title);
2137       bookmark->title = g_strdup (title);
2138     }
2139   else
2140     {
2141       BookmarkItem *item;
2142       
2143       item = g_bookmark_file_lookup_item (bookmark, uri);
2144       if (!item)
2145         {
2146           item = bookmark_item_new (uri);
2147           g_bookmark_file_add_item (bookmark, item, NULL);
2148         }
2149       
2150       g_free (item->title);
2151       item->title = g_strdup (title);
2152       
2153       item->modified = time (NULL);
2154     }
2155 }
2156
2157 /**
2158  * g_bookmark_file_get_title:
2159  * @bookmark: a #GBookmarkFile
2160  * @uri: a valid URI or %NULL
2161  * @error: return location for a #GError, or %NULL
2162  *
2163  * Returns the title of the bookmark for @uri.
2164  *
2165  * If @uri is %NULL, the title of @bookmark is returned.
2166  *
2167  * In the event the URI cannot be found, %NULL is returned and
2168  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
2169  *
2170  * Return value: a newly allocated string or %NULL if the specified
2171  *   URI cannot be found.
2172  *
2173  * Since: 2.12
2174  */
2175 gchar *
2176 g_bookmark_file_get_title (GBookmarkFile  *bookmark,
2177                            const gchar    *uri,
2178                            GError        **error)
2179 {
2180   BookmarkItem *item;
2181   
2182   g_return_val_if_fail (bookmark != NULL, NULL);
2183   
2184   if (!uri)
2185     return g_strdup (bookmark->title);
2186   
2187   item = g_bookmark_file_lookup_item (bookmark, uri);
2188   if (!item)
2189     {
2190       g_set_error (error, G_BOOKMARK_FILE_ERROR,
2191                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
2192                    _("No bookmark found for URI '%s'"),
2193                    uri);
2194       return NULL;
2195     }
2196   
2197   return g_strdup (item->title);
2198 }
2199
2200 /**
2201  * g_bookmark_file_set_description:
2202  * @bookmark: a #GBookmarkFile
2203  * @uri: a valid URI or %NULL
2204  * @description: a string
2205  *
2206  * Sets @description as the description of the bookmark for @uri.
2207  *
2208  * If @uri is %NULL, the description of @bookmark is set.
2209  *
2210  * If a bookmark for @uri cannot be found then it is created.
2211  *
2212  * Since: 2.12
2213  */
2214 void
2215 g_bookmark_file_set_description (GBookmarkFile *bookmark,
2216                                  const gchar   *uri,
2217                                  const gchar   *description)
2218 {
2219   g_return_if_fail (bookmark != NULL);
2220
2221   if (!uri)
2222     {
2223       g_free (bookmark->description); 
2224       bookmark->description = g_strdup (description);
2225     }
2226   else
2227     {
2228       BookmarkItem *item;
2229       
2230       item = g_bookmark_file_lookup_item (bookmark, uri);
2231       if (!item)
2232         {
2233           item = bookmark_item_new (uri);
2234           g_bookmark_file_add_item (bookmark, item, NULL);
2235         }
2236       
2237       g_free (item->description);
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 '\0':
3294          goto out;
3295        case 'u':
3296          g_string_append (exec, uri);
3297          break;
3298        case 'f':
3299          {
3300            gchar *file = g_filename_from_uri (uri, NULL, NULL);
3301            g_string_append (exec, file);
3302            g_free (file);
3303          }
3304          break;
3305        case '%':
3306        default:
3307          exec = g_string_append_c (exec, ch);
3308          break;
3309        }
3310    }
3311    
3312  out:
3313   return g_string_free (exec, FALSE);
3314 }
3315
3316 /**
3317  * g_bookmark_file_get_app_info:
3318  * @bookmark: a #GBookmarkFile
3319  * @uri: a valid URI
3320  * @name: an application's name
3321  * @exec: location for the command line of the application, or %NULL
3322  * @count: return location for the registration count, or %NULL
3323  * @stamp: return location for the last registration time, or %NULL
3324  * @error: return location for a #GError, or %NULL
3325  *
3326  * Gets the registration informations of @app_name for the bookmark for
3327  * @uri.  See g_bookmark_file_set_app_info() for more informations about
3328  * the returned data.
3329  *
3330  * The string returned in @app_exec must be freed.
3331  *
3332  * In the event the URI cannot be found, %FALSE is returned and
3333  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.  In the
3334  * event that no application with name @app_name has registered a bookmark
3335  * for @uri,  %FALSE is returned and error is set to
3336  * #G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED.
3337  *
3338  * Return value: %TRUE on success.
3339  *
3340  * Since: 2.12
3341  */
3342 gboolean
3343 g_bookmark_file_get_app_info (GBookmarkFile  *bookmark,
3344                               const gchar    *uri,
3345                               const gchar    *name,
3346                               gchar         **exec,
3347                               guint          *count,
3348                               time_t         *stamp,
3349                               GError        **error)
3350 {
3351   BookmarkItem *item;
3352   BookmarkAppInfo *ai;
3353   
3354   g_return_val_if_fail (bookmark != NULL, FALSE);
3355   g_return_val_if_fail (uri != NULL, FALSE);
3356   g_return_val_if_fail (name != NULL, FALSE);
3357   
3358   item = g_bookmark_file_lookup_item (bookmark, uri);
3359   if (!item)
3360     {
3361       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3362                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3363                    _("No bookmark found for URI '%s'"),
3364                    uri);
3365       return FALSE;
3366     }
3367   
3368   ai = bookmark_item_lookup_app_info (item, name);
3369   if (!ai)
3370     {
3371       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3372                    G_BOOKMARK_FILE_ERROR_APP_NOT_REGISTERED,
3373                    _("No application with name '%s' registered a bookmark for '%s'"),
3374                    name,
3375                    uri);
3376       return FALSE;
3377     }
3378   
3379   if (exec)
3380     *exec = expand_exec_line (ai->exec, uri);
3381   
3382   if (count)
3383     *count = ai->count;
3384   
3385   if (stamp)
3386     *stamp = ai->stamp;
3387   
3388   return TRUE;
3389 }
3390
3391 /**
3392  * g_bookmark_file_get_applications:
3393  * @bookmark: a #GBookmarkFile
3394  * @uri: a valid URI
3395  * @length: return location of the length of the returned list, or %NULL
3396  * @error: return location for a #GError, or %NULL
3397  *
3398  * Retrieves the names of the applications that have registered the
3399  * bookmark for @uri.
3400  * 
3401  * In the event the URI cannot be found, %NULL is returned and
3402  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3403  *
3404  * Return value: a newly allocated %NULL-terminated array of strings.
3405  *   Use g_strfreev() to free it.
3406  *
3407  * Since: 2.12
3408  */
3409 gchar **
3410 g_bookmark_file_get_applications (GBookmarkFile  *bookmark,
3411                                   const gchar    *uri,
3412                                   gsize          *length,
3413                                   GError        **error)
3414 {
3415   BookmarkItem *item;
3416   GList *l;
3417   gchar **apps;
3418   gsize i, n_apps;
3419   
3420   g_return_val_if_fail (bookmark != NULL, NULL);
3421   g_return_val_if_fail (uri != NULL, NULL);
3422   
3423   item = g_bookmark_file_lookup_item (bookmark, uri);
3424   if (!item)
3425     {
3426       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3427                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3428                    _("No bookmark found for URI '%s'"),
3429                    uri);
3430       return NULL;
3431     }
3432   
3433   if (!item->metadata)
3434     {      
3435       if (length)
3436         *length = 0;
3437       
3438       return NULL;
3439     }
3440   
3441   n_apps = g_list_length (item->metadata->applications);
3442   apps = g_new0 (gchar *, n_apps + 1);
3443   
3444   for (l = g_list_last (item->metadata->applications), i = 0;
3445        l != NULL;
3446        l = l->prev)
3447     {
3448       BookmarkAppInfo *ai;
3449       
3450       ai = (BookmarkAppInfo *) l->data;
3451       
3452       g_assert (ai != NULL);
3453       g_assert (ai->name != NULL);
3454       
3455       apps[i++] = g_strdup (ai->name);
3456     }
3457   apps[i] = NULL;
3458   
3459   if (length)
3460     *length = i;
3461   
3462   return apps;
3463 }
3464
3465 /**
3466  * g_bookmark_file_get_size:
3467  * @bookmark: a #GBookmarkFile
3468  * 
3469  * Gets the number of bookmarks inside @bookmark.
3470  * 
3471  * Return value: the number of bookmarks
3472  *
3473  * Since: 2.12
3474  */
3475 gint
3476 g_bookmark_file_get_size (GBookmarkFile *bookmark)
3477 {
3478   g_return_val_if_fail (bookmark != NULL, 0);
3479
3480   return g_list_length (bookmark->items);
3481 }
3482
3483 /**
3484  * g_bookmark_file_move_item:
3485  * @bookmark: a #GBookmarkFile
3486  * @old_uri: a valid URI
3487  * @new_uri: a valid URI, or %NULL
3488  * @error: return location for a #GError or %NULL
3489  *
3490  * Changes the URI of a bookmark item from @old_uri to @new_uri.  Any
3491  * existing bookmark for @new_uri will be overwritten.  If @new_uri is
3492  * %NULL, then the bookmark is removed.
3493  *
3494  * In the event the URI cannot be found, %FALSE is returned and
3495  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3496  *
3497  * Return value: %TRUE if the URI was successfully changed
3498  *
3499  * Since: 2.12
3500  */
3501 gboolean
3502 g_bookmark_file_move_item (GBookmarkFile  *bookmark,
3503                            const gchar    *old_uri,
3504                            const gchar    *new_uri,
3505                            GError        **error)
3506 {
3507   BookmarkItem *item;
3508   GError *remove_error;
3509   
3510   g_return_val_if_fail (bookmark != NULL, FALSE);
3511   g_return_val_if_fail (old_uri != NULL, FALSE);
3512   
3513   item = g_bookmark_file_lookup_item (bookmark, old_uri);
3514   if (!item)
3515     {
3516       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3517                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3518                    _("No bookmark found for URI '%s'"),
3519                    old_uri);
3520       return FALSE;
3521     }
3522
3523   if (new_uri && new_uri[0] != '\0')
3524     {
3525       if (g_bookmark_file_has_item (bookmark, new_uri))
3526         {
3527           remove_error = NULL;
3528           g_bookmark_file_remove_item (bookmark, new_uri, &remove_error);
3529           if (remove_error)
3530             {
3531               g_propagate_error (error, remove_error);
3532               
3533               return FALSE;
3534             }
3535         }
3536       
3537       g_free (item->uri);
3538       item->uri = g_strdup (new_uri);
3539       item->modified = time (NULL);
3540       
3541       return TRUE;
3542     }
3543   else
3544     {
3545       remove_error = NULL;
3546       g_bookmark_file_remove_item (bookmark, old_uri, &remove_error);
3547       if (remove_error)
3548         {
3549           g_propagate_error (error, remove_error);
3550           
3551           return FALSE;
3552         }
3553     }
3554   
3555   return FALSE;
3556 }
3557
3558 /**
3559  * g_bookmark_file_set_icon:
3560  * @bookmark: a #GBookmarkFile
3561  * @uri: a valid URI
3562  * @href: the URI of the icon for the bookmark, or %NULL
3563  * @mime_type: the MIME type of the icon for the bookmark
3564  *
3565  * Sets the icon for the bookmark for @uri.  If @href is %NULL, unsets
3566  * the currently set icon.
3567  *
3568  * If no bookmark for @uri is found it is created.
3569  *
3570  * Since: 2.12
3571  */
3572 void
3573 g_bookmark_file_set_icon (GBookmarkFile *bookmark,
3574                           const gchar   *uri,
3575                           const gchar   *href,
3576                           const gchar   *mime_type)
3577 {
3578   BookmarkItem *item;
3579   
3580   g_return_if_fail (bookmark != NULL);
3581   g_return_if_fail (uri != NULL);
3582
3583   item = g_bookmark_file_lookup_item (bookmark, uri);
3584   if (!item)
3585     {
3586       item = bookmark_item_new (uri);
3587       g_bookmark_file_add_item (bookmark, item, NULL);
3588     }
3589   
3590   if (!item->metadata)
3591     item->metadata = bookmark_metadata_new ();
3592   
3593   g_free (item->metadata->icon_href);
3594   g_free (item->metadata->icon_mime);
3595   
3596   item->metadata->icon_href = g_strdup (href);
3597   
3598   if (mime_type && mime_type[0] != '\0')
3599     item->metadata->icon_mime = g_strdup (mime_type);
3600   else
3601     item->metadata->icon_mime = g_strdup ("application/octet-stream");
3602   
3603   item->modified = time (NULL);
3604 }
3605
3606 /**
3607  * g_bookmark_file_get_icon:
3608  * @bookmark: a #GBookmarkFile
3609  * @uri: a valid URI
3610  * @href: return location for the icon's location or %NULL
3611  * @mime_type: return location for the icon's MIME type or %NULL
3612  * @error: return location for a #GError or %NULL
3613  *
3614  * Gets the icon of the bookmark for @uri.
3615  *
3616  * In the event the URI cannot be found, %FALSE is returned and
3617  * @error is set to #G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND.
3618  *
3619  * Return value: %TRUE if the icon for the bookmark for the URI was found.
3620  *   You should free the returned strings.
3621  *
3622  * Since: 2.12
3623  */
3624 gboolean
3625 g_bookmark_file_get_icon (GBookmarkFile  *bookmark,
3626                           const gchar    *uri,
3627                           gchar         **href,
3628                           gchar         **mime_type,
3629                           GError        **error)
3630 {
3631   BookmarkItem *item;
3632   
3633   g_return_val_if_fail (bookmark != NULL, FALSE);
3634   g_return_val_if_fail (uri != NULL, FALSE);
3635   
3636   item = g_bookmark_file_lookup_item (bookmark, uri);
3637   if (!item)
3638     {
3639       g_set_error (error, G_BOOKMARK_FILE_ERROR,
3640                    G_BOOKMARK_FILE_ERROR_URI_NOT_FOUND,
3641                    _("No bookmark found for URI '%s'"),
3642                    uri);
3643       return FALSE;
3644     }
3645   
3646   if ((!item->metadata) || (!item->metadata->icon_href))
3647     return FALSE;
3648   
3649   if (href)
3650     *href = g_strdup (item->metadata->icon_href);
3651   
3652   if (mime_type)
3653     *mime_type = g_strdup (item->metadata->icon_mime);
3654   
3655   return TRUE;
3656 }
3657
3658 #define __G_BOOKMARK_FILE_C__
3659 #include "galiasdef.c"