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