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