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