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