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