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

source code of gtk/subprojects/glib/glib/gbookmarkfile.c