1 /* Internal bookmarks support */
13 #include "bfu/dialog.h"
14 #include "bfu/hierbox.h"
15 #include "bfu/listbox.h"
16 #include "bookmarks/backend/common.h"
17 #include "bookmarks/bookmarks.h"
18 #include "bookmarks/dialogs.h"
19 #include "config/home.h"
20 #include "config/options.h"
21 #include "intl/gettext/libintl.h"
22 #include "main/module.h"
23 #include "main/object.h"
24 #include "protocol/uri.h"
25 #include "session/task.h"
26 #include "terminal/tab.h"
27 #include "util/conv.h"
28 #include "util/hash.h"
29 #include "util/lists.h"
30 #include "util/memory.h"
31 #include "util/secsave.h"
32 #include "util/string.h"
34 /* The list of bookmarks */
35 INIT_LIST_OF(struct bookmark
, bookmarks
);
37 /* Set to 1, if bookmarks have changed. */
38 static int bookmarks_dirty
= 0;
40 static struct hash
*bookmark_cache
= NULL
;
42 static struct bookmark
*bm_snapshot_last_folder
;
47 static union option_info bookmark_options_info
[] = {
48 INIT_OPT_TREE("", N_("Bookmarks"),
50 N_("Bookmark options.")),
52 #ifdef CONFIG_XBEL_BOOKMARKS
53 INIT_OPT_INT("bookmarks", N_("File format"),
54 "file_format", 0, 0, 1, 0,
55 N_("File format for bookmarks (affects both reading and "
57 "0 is the default native ELinks format\n"
58 "1 is XBEL universal XML bookmarks format")),
60 INIT_OPT_INT("bookmarks", N_("File format"),
61 "file_format", 0, 0, 1, 0,
62 N_("File format for bookmarks (affects both reading and "
64 "0 is the default native ELinks format\n"
65 "1 is XBEL universal XML bookmarks format (DISABLED)")),
68 INIT_OPT_BOOL("bookmarks", N_("Save folder state"),
70 N_("When saving bookmarks also store whether folders are "
71 "expanded or not, so the look of the bookmark dialog is "
72 "kept across ELinks sessions. If disabled all folders will "
73 "appear unexpanded next time ELinks is run.")),
75 INIT_OPT_BOOL("ui.sessions", N_("Periodic snapshotting"),
77 N_("Automatically save a snapshot of all tabs periodically. "
78 "This will periodically bookmark the tabs of each terminal "
79 "in a separate folder for recovery after a crash.\n"
81 "This feature requires bookmark support.")),
86 static enum evhook_status
bookmark_change_hook(va_list ap
, void *data
);
87 static enum evhook_status
bookmark_write_hook(va_list ap
, void *data
);
89 struct event_hook_info bookmark_hooks
[] = {
90 { "bookmark-delete", 0, bookmark_change_hook
, NULL
},
91 { "bookmark-move", 0, bookmark_change_hook
, NULL
},
92 { "bookmark-update", 0, bookmark_change_hook
, NULL
},
93 { "periodic-saving", 0, bookmark_write_hook
, NULL
},
98 static enum evhook_status
99 bookmark_change_hook(va_list ap
, void *data
)
101 struct bookmark
*bookmark
= va_arg(ap
, struct bookmark
*);
103 if (bookmark
== bm_snapshot_last_folder
)
104 bm_snapshot_last_folder
= NULL
;
106 return EVENT_HOOK_STATUS_NEXT
;
109 static void bookmark_snapshot();
111 static enum evhook_status
112 bookmark_write_hook(va_list ap
, void *data
)
114 if (get_opt_bool("ui.sessions.snapshot", NULL
)
115 && !get_cmd_opt_bool("anonymous"))
120 return EVENT_HOOK_STATUS_NEXT
;
125 change_hook_folder_state(struct session
*ses
, struct option
*current
,
126 struct option
*changed
)
128 if (!changed
->value
.number
) {
129 /* We are to collapse all folders on exit; mark bookmarks dirty
130 * to ensure that this will happen. */
131 bookmarks_set_dirty();
138 init_bookmarks(struct module
*module
)
140 static const struct change_hook_info bookmarks_change_hooks
[] = {
141 { "bookmarks.folder_state", change_hook_folder_state
},
145 register_change_hooks(bookmarks_change_hooks
);
150 /* Clears the bookmark list */
152 free_bookmarks(LIST_OF(struct bookmark
) *bookmarks_list
,
153 LIST_OF(struct listbox_item
) *box_items
)
157 foreach (bm
, *bookmarks_list
) {
158 if (!list_empty(bm
->child
))
159 free_bookmarks(&bm
->child
, &bm
->box_item
->child
);
164 free_list(*box_items
);
165 free_list(*bookmarks_list
);
166 if (bookmark_cache
) free_hash(&bookmark_cache
);
169 /* Does final cleanup and saving of bookmarks */
171 done_bookmarks(struct module
*module
)
173 /* This is a clean shutdown, so delete the last snapshot. */
174 if (bm_snapshot_last_folder
) delete_bookmark(bm_snapshot_last_folder
);
175 bm_snapshot_last_folder
= NULL
;
178 free_bookmarks(&bookmarks
, &bookmark_browser
.root
.child
);
179 free_last_searched_bookmark();
182 struct module bookmarks_module
= struct_module(
183 /* name: */ N_("Bookmarks"),
184 /* options: */ bookmark_options_info
,
185 /* hooks: */ bookmark_hooks
,
186 /* submodules: */ NULL
,
188 /* init: */ init_bookmarks
,
189 /* done: */ done_bookmarks
194 /* Read/write wrappers */
196 /* Loads the bookmarks from file */
204 write_bookmarks(void)
206 if (get_cmd_opt_bool("anonymous")) {
207 bookmarks_unset_dirty();
210 bookmarks_write(&bookmarks
);
216 /* Bookmarks manipulation */
219 bookmarks_set_dirty(void)
225 bookmarks_unset_dirty(void)
231 bookmarks_are_dirty(void)
233 return (bookmarks_dirty
== 1);
236 #define check_bookmark_cache(url) (bookmark_cache && (url) && *(url))
239 done_bookmark(struct bookmark
*bm
)
241 done_listbox_item(&bookmark_browser
, bm
->box_item
);
249 delete_bookmark(struct bookmark
*bm
)
251 static int delete_bookmark_event_id
= EVENT_NONE
;
253 while (!list_empty(bm
->child
)) {
254 delete_bookmark(bm
->child
.next
);
257 if (check_bookmark_cache(bm
->url
)) {
258 struct hash_item
*item
;
260 item
= get_hash_item(bookmark_cache
, bm
->url
, strlen(bm
->url
));
261 if (item
) del_hash_item(bookmark_cache
, item
);
264 set_event_id(delete_bookmark_event_id
, "bookmark-delete");
265 trigger_event(delete_bookmark_event_id
, bm
);
268 bookmarks_set_dirty();
273 /** Deletes any bookmarks with no URLs (i.e., folders) and of which
274 * the title matches the given argument.
277 * The title of the folder, in UTF-8. */
279 delete_folder_by_name(const unsigned char *foldername
)
281 struct bookmark
*bookmark
, *next
;
283 foreachsafe (bookmark
, next
, bookmarks
) {
284 if ((bookmark
->url
&& *bookmark
->url
)
285 || strcmp(bookmark
->title
, foldername
))
288 delete_bookmark(bookmark
);
292 /** Allocate and initialize a bookmark in the given folder. This
293 * however does not set bookmark.box_item; use add_bookmark() for
297 * The folder in which to add the bookmark, or NULL to add it at
300 * Title of the bookmark. Must be in UTF-8 and not NULL.
301 * "-" means add a separator.
303 * URL to which the bookmark will point. Must be in UTF-8.
304 * NULL or "" means add a bookmark folder.
306 * @return the new bookmark, or NULL on error. */
307 static struct bookmark
*
308 init_bookmark(struct bookmark
*root
, unsigned char *title
, unsigned char *url
)
312 bm
= mem_calloc(1, sizeof(*bm
));
313 if (!bm
) return NULL
;
315 bm
->title
= stracpy(title
);
320 sanitize_title(bm
->title
);
322 bm
->url
= stracpy(empty_string_or_(url
));
328 sanitize_url(bm
->url
);
331 init_list(bm
->child
);
333 object_nolock(bm
, "bookmark");
339 add_bookmark_item_to_bookmarks(struct bookmark
*bm
, struct bookmark
*root
, int place
)
341 /* Actually add it */
344 add_to_list_end(root
->child
, bm
);
346 add_to_list_end(bookmarks
, bm
);
349 add_to_list(root
->child
, bm
);
351 add_to_list(bookmarks
, bm
);
353 bookmarks_set_dirty();
355 /* Hash creation if needed. */
357 bookmark_cache
= init_hash8();
359 /* Create a new entry. */
360 if (check_bookmark_cache(bm
->url
))
361 add_hash_item(bookmark_cache
, bm
->url
, strlen(bm
->url
), bm
);
364 /** Add a bookmark to the bookmark list.
367 * The folder in which to add the bookmark, or NULL to add it at
370 * 0 means add to the top. 1 means add to the bottom.
372 * Title of the bookmark. Must be in UTF-8 and not NULL.
373 * "-" means add a separator.
375 * URL to which the bookmark will point. Must be in UTF-8.
376 * NULL or "" means add a bookmark folder.
378 * @return the new bookmark, or NULL on error.
380 * @see add_bookmark_cp() */
382 add_bookmark(struct bookmark
*root
, int place
, unsigned char *title
,
385 enum listbox_item_type type
;
388 bm
= init_bookmark(root
, title
, url
);
389 if (!bm
) return NULL
;
393 } else if (title
&& title
[0] == '-' && title
[1] == '\0') {
399 bm
->box_item
= add_listbox_item(&bookmark_browser
,
400 root
? root
->box_item
: NULL
,
412 add_bookmark_item_to_bookmarks(bm
, root
, place
);
417 /** Add a bookmark to the bookmark list.
420 * The folder in which to add the bookmark, or NULL to add it at
423 * 0 means add to the top. 1 means add to the bottom.
425 * Codepage of @a title and @a url.
427 * Title of the bookmark. Must not be NULL.
428 * "-" means add a separator.
430 * URL to which the bookmark will point.
431 * NULL or "" means add a bookmark folder.
433 * @return the new bookmark.
435 * @see add_bookmark() */
437 add_bookmark_cp(struct bookmark
*root
, int place
, int codepage
,
438 unsigned char *title
, unsigned char *url
)
440 const int utf8_cp
= get_cp_index("UTF-8");
441 struct conv_table
*table
;
442 unsigned char *utf8_title
= NULL
;
443 unsigned char *utf8_url
= NULL
;
444 struct bookmark
*bookmark
= NULL
;
449 table
= get_translation_table(codepage
, utf8_cp
);
453 utf8_title
= convert_string(table
, title
, strlen(title
),
456 utf8_url
= convert_string(table
, url
, strlen(url
),
459 if (utf8_title
&& utf8_url
)
460 bookmark
= add_bookmark(root
, place
,
461 utf8_title
, utf8_url
);
462 mem_free_if(utf8_title
);
463 mem_free_if(utf8_url
);
467 /* Updates an existing bookmark.
469 * If there's any problem, return 0. Otherwise, return 1.
471 * If any of the fields are NULL, the value is left unchanged. */
473 update_bookmark(struct bookmark
*bm
, int codepage
,
474 unsigned char *title
, unsigned char *url
)
476 static int update_bookmark_event_id
= EVENT_NONE
;
477 const int utf8_cp
= get_cp_index("UTF-8");
478 struct conv_table
*table
;
479 unsigned char *title2
= NULL
;
480 unsigned char *url2
= NULL
;
482 table
= get_translation_table(codepage
, utf8_cp
);
487 url2
= convert_string(table
, url
, strlen(url
),
495 title2
= convert_string(table
, title
, strlen(title
),
502 sanitize_title(title2
);
505 set_event_id(update_bookmark_event_id
, "bookmark-update");
506 trigger_event(update_bookmark_event_id
, bm
, title2
, url2
);
509 mem_free_set(&bm
->title
, title2
);
513 if (check_bookmark_cache(bm
->url
)) {
514 struct hash_item
*item
;
515 int len
= strlen(bm
->url
);
517 item
= get_hash_item(bookmark_cache
, bm
->url
, len
);
518 if (item
) del_hash_item(bookmark_cache
, item
);
521 if (check_bookmark_cache(url2
)) {
522 add_hash_item(bookmark_cache
, url2
, strlen(url2
), bm
);
525 mem_free_set(&bm
->url
, url2
);
528 bookmarks_set_dirty();
533 /** Search for a bookmark with the given title. The search does not
534 * recurse into subfolders.
537 * Search in this folder. NULL means search in the root.
540 * Search for this title. Must be in UTF-8 and not NULL.
542 * @return The bookmark, or NULL if not found. */
544 get_bookmark_by_name(struct bookmark
*folder
, unsigned char *title
)
546 struct bookmark
*bookmark
;
547 LIST_OF(struct bookmark
) *lh
;
549 lh
= folder
? &folder
->child
: &bookmarks
;
551 foreach (bookmark
, *lh
)
552 if (!strcmp(bookmark
->title
, title
)) return bookmark
;
557 /* Search bookmark cache for item matching url. */
559 get_bookmark(unsigned char *url
)
561 struct hash_item
*item
;
563 /** @todo Bug 1066: URLs in bookmark_cache should be UTF-8 */
564 if (!check_bookmark_cache(url
))
567 /* Search for cached entry. */
569 item
= get_hash_item(bookmark_cache
, url
, strlen(url
));
571 return item
? item
->value
: NULL
;
575 bookmark_terminal(struct terminal
*term
, struct bookmark
*folder
)
577 unsigned char title
[MAX_STR_LEN
], url
[MAX_STR_LEN
];
579 int term_cp
= get_terminal_codepage(term
);
581 foreachback_tab (tab
, term
->windows
) {
582 struct session
*ses
= tab
->data
;
584 if (!get_current_url(ses
, url
, MAX_STR_LEN
))
587 if (!get_current_title(ses
, title
, MAX_STR_LEN
))
590 add_bookmark_cp(folder
, 1, term_cp
, title
, url
);
594 /** Create a bookmark for each document on the specified terminal,
595 * and a folder to contain those bookmarks.
598 * The terminal whose open documents should be bookmarked.
601 * The name of the new bookmark folder, in UTF-8. */
603 bookmark_terminal_tabs(struct terminal
*term
, unsigned char *foldername
)
605 struct bookmark
*folder
= add_bookmark(NULL
, 1, foldername
, NULL
);
609 bookmark_terminal(term
, folder
);
613 bookmark_all_terminals(struct bookmark
*folder
)
616 struct terminal
*term
;
618 if (get_cmd_opt_bool("anonymous"))
621 if (list_is_singleton(terminals
)) {
622 bookmark_terminal(terminals
.next
, folder
);
626 foreach (term
, terminals
) {
627 unsigned char subfoldername
[4];
628 struct bookmark
*subfolder
;
630 if (ulongcat(subfoldername
, NULL
, n
, sizeof(subfoldername
), 0)
631 >= sizeof(subfoldername
))
636 /* Because subfoldername[] contains only digits,
637 * it is OK as UTF-8. */
638 subfolder
= add_bookmark(folder
, 1, subfoldername
, NULL
);
639 if (!subfolder
) return;
641 bookmark_terminal(term
, subfolder
);
647 get_auto_save_bookmark_foldername_utf8(void)
649 unsigned char *foldername
;
651 struct conv_table
*convert_table
;
653 foldername
= get_opt_str("ui.sessions.auto_save_foldername", NULL
);
654 if (!*foldername
) return NULL
;
656 /* The charset of the string returned by get_opt_str()
657 * seems to be documented nowhere. Let's assume it is
658 * the system charset. */
659 from_cp
= get_cp_index("System");
660 to_cp
= get_cp_index("UTF-8");
661 convert_table
= get_translation_table(from_cp
, to_cp
);
662 if (!convert_table
) return NULL
;
664 return convert_string(convert_table
,
665 foldername
, strlen(foldername
),
671 bookmark_auto_save_tabs(struct terminal
*term
)
673 unsigned char *foldername
; /* UTF-8 */
675 if (get_cmd_opt_bool("anonymous")
676 || !get_opt_bool("ui.sessions.auto_save", NULL
))
679 foldername
= get_auto_save_bookmark_foldername_utf8();
680 if (!foldername
) return;
682 /* Ensure uniqueness of the auto save folder, so it is possible to
683 * restore the (correct) session when starting up. */
684 delete_folder_by_name(foldername
);
686 bookmark_terminal_tabs(term
, foldername
);
687 mem_free(foldername
);
691 bookmark_snapshot(void)
693 struct string folderstring
;
694 struct bookmark
*folder
;
696 if (!init_string(&folderstring
)) return;
698 add_to_string(&folderstring
, "Session snapshot");
701 add_to_string(&folderstring
, " - ");
702 add_date_to_string(&folderstring
, get_opt_str("ui.date_format", NULL
),
706 /* folderstring must be in the system codepage because
707 * add_date_to_string() uses strftime(). */
708 folder
= add_bookmark_cp(NULL
, 1, get_cp_index("System"),
709 folderstring
.source
, NULL
);
710 done_string(&folderstring
);
713 bookmark_all_terminals(folder
);
715 if (bm_snapshot_last_folder
) delete_bookmark(bm_snapshot_last_folder
);
716 bm_snapshot_last_folder
= folder
;
720 /** Open all bookmarks from the named folder.
723 * The session in which to open the first bookmark. The other
724 * bookmarks of the folder open in new tabs on the same terminal.
727 * The name of the bookmark folder, in UTF-8. */
729 open_bookmark_folder(struct session
*ses
, unsigned char *foldername
)
731 struct bookmark
*bookmark
;
732 struct bookmark
*folder
= NULL
;
733 struct bookmark
*current
= NULL
;
735 assert(foldername
&& ses
);
736 if_assert_failed
return;
738 foreach (bookmark
, bookmarks
) {
739 if (bookmark
->box_item
->type
!= BI_FOLDER
)
741 if (strcmp(bookmark
->title
, foldername
))
749 foreach (bookmark
, folder
->child
) {
752 if (bookmark
->box_item
->type
== BI_FOLDER
753 || bookmark
->box_item
->type
== BI_SEPARATOR
757 /** @todo Bug 1066: Tell the URI layer that
758 * bookmark->url is UTF-8. */
759 uri
= get_translated_uri(bookmark
->url
, NULL
);
762 /* Save the first bookmark for the current tab */
767 open_uri_in_new_tab(ses
, uri
, 1, 0);