bug 764: Initialize the right member of union option_value
[elinks.git] / src / bookmarks / bookmarks.c
blobf0903c3b709f7990eea0e23f2a5792787a7571fa
1 /* Internal bookmarks support */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
11 #include "elinks.h"
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;
45 /* Life functions */
47 static union option_info bookmark_options_info[] = {
48 INIT_OPT_TREE("", N_("Bookmarks"),
49 "bookmarks", 0,
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 "
56 "saving):\n"
57 "0 is the default native ELinks format\n"
58 "1 is XBEL universal XML bookmarks format")),
59 #else
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 "
63 "saving):\n"
64 "0 is the default native ELinks format\n"
65 "1 is XBEL universal XML bookmarks format (DISABLED)")),
66 #endif
68 INIT_OPT_BOOL("bookmarks", N_("Save folder state"),
69 "folder_state", 0, 1,
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"),
76 "snapshot", 0, 0,
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"
80 "\n"
81 "This feature requires bookmark support.")),
83 NULL_OPTION_INFO
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 },
95 NULL_EVENT_HOOK_INFO,
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")
115 && !get_cmd_opt_bool("anonymous"))
116 bookmark_snapshot();
118 write_bookmarks();
120 return EVENT_HOOK_STATUS_NEXT;
124 static int
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();
134 return 0;
137 static void
138 init_bookmarks(struct module *module)
140 static const struct change_hook_info bookmarks_change_hooks[] = {
141 { "bookmarks.folder_state", change_hook_folder_state },
142 { NULL, NULL },
145 register_change_hooks(bookmarks_change_hooks);
147 read_bookmarks();
150 /* Clears the bookmark list */
151 static void
152 free_bookmarks(LIST_OF(struct bookmark) *bookmarks_list,
153 LIST_OF(struct listbox_item) *box_items)
155 struct bookmark *bm;
157 foreach (bm, *bookmarks_list) {
158 if (!list_empty(bm->child))
159 free_bookmarks(&bm->child, &bm->box_item->child);
160 mem_free(bm->title);
161 mem_free(bm->url);
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 */
170 static void
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;
177 write_bookmarks();
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,
187 /* data: */ NULL,
188 /* init: */ init_bookmarks,
189 /* done: */ done_bookmarks
194 /* Read/write wrappers */
196 /* Loads the bookmarks from file */
197 void
198 read_bookmarks(void)
200 bookmarks_read();
203 void
204 write_bookmarks(void)
206 if (get_cmd_opt_bool("anonymous")) {
207 bookmarks_unset_dirty();
208 return;
210 bookmarks_write(&bookmarks);
216 /* Bookmarks manipulation */
218 void
219 bookmarks_set_dirty(void)
221 bookmarks_dirty = 1;
224 void
225 bookmarks_unset_dirty(void)
227 bookmarks_dirty = 0;
231 bookmarks_are_dirty(void)
233 return (bookmarks_dirty == 1);
236 #define check_bookmark_cache(url) (bookmark_cache && (url) && *(url))
238 static void
239 done_bookmark(struct bookmark *bm)
241 done_listbox_item(&bookmark_browser, bm->box_item);
243 mem_free(bm->title);
244 mem_free(bm->url);
245 mem_free(bm);
248 void
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);
267 del_from_list(bm);
268 bookmarks_set_dirty();
270 done_bookmark(bm);
273 /** Deletes any bookmarks with no URLs (i.e., folders) and of which
274 * the title matches the given argument.
276 * @param foldername
277 * The title of the folder, in UTF-8. */
278 static void
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))
286 continue;
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
294 * that.
296 * @param root
297 * The folder in which to add the bookmark, or NULL to add it at
298 * top level.
299 * @param title
300 * Title of the bookmark. Must be in UTF-8 and not NULL.
301 * "-" means add a separator.
302 * @param url
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)
310 struct bookmark *bm;
312 bm = mem_calloc(1, sizeof(*bm));
313 if (!bm) return NULL;
315 bm->title = stracpy(title);
316 if (!bm->title) {
317 mem_free(bm);
318 return NULL;
320 sanitize_title(bm->title);
322 bm->url = stracpy(empty_string_or_(url));
323 if (!bm->url) {
324 mem_free(bm->title);
325 mem_free(bm);
326 return NULL;
328 sanitize_url(bm->url);
330 bm->root = root;
331 init_list(bm->child);
333 object_nolock(bm, "bookmark");
335 return bm;
338 static void
339 add_bookmark_item_to_bookmarks(struct bookmark *bm, struct bookmark *root, int place)
341 /* Actually add it */
342 if (place) {
343 if (root)
344 add_to_list_end(root->child, bm);
345 else
346 add_to_list_end(bookmarks, bm);
347 } else {
348 if (root)
349 add_to_list(root->child, bm);
350 else
351 add_to_list(bookmarks, bm);
353 bookmarks_set_dirty();
355 /* Hash creation if needed. */
356 if (!bookmark_cache)
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.
366 * @param root
367 * The folder in which to add the bookmark, or NULL to add it at
368 * top level.
369 * @param place
370 * 0 means add to the top. 1 means add to the bottom.
371 * @param title
372 * Title of the bookmark. Must be in UTF-8 and not NULL.
373 * "-" means add a separator.
374 * @param url
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() */
381 struct bookmark *
382 add_bookmark(struct bookmark *root, int place, unsigned char *title,
383 unsigned char *url)
385 enum listbox_item_type type;
386 struct bookmark *bm;
388 bm = init_bookmark(root, title, url);
389 if (!bm) return NULL;
391 if (url && *url) {
392 type = BI_LEAF;
393 } else if (title && title[0] == '-' && title[1] == '\0') {
394 type = BI_SEPARATOR;
395 } else {
396 type = BI_FOLDER;
399 bm->box_item = add_listbox_item(&bookmark_browser,
400 root ? root->box_item : NULL,
401 type,
402 (void *) bm,
403 place ? -1 : 1);
405 if (!bm->box_item) {
406 mem_free(bm->url);
407 mem_free(bm->title);
408 mem_free(bm);
409 return NULL;
412 add_bookmark_item_to_bookmarks(bm, root, place);
414 return bm;
417 /** Add a bookmark to the bookmark list.
419 * @param root
420 * The folder in which to add the bookmark, or NULL to add it at
421 * top level.
422 * @param place
423 * 0 means add to the top. 1 means add to the bottom.
424 * @param codepage
425 * Codepage of @a title and @a url.
426 * @param title
427 * Title of the bookmark. Must not be NULL.
428 * "-" means add a separator.
429 * @param url
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() */
436 struct 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;
446 if (!url)
447 url = "";
449 table = get_translation_table(codepage, utf8_cp);
450 if (!table)
451 return NULL;
453 utf8_title = convert_string(table, title, strlen(title),
454 utf8_cp, CSM_NONE,
455 NULL, NULL, NULL);
456 utf8_url = convert_string(table, url, strlen(url),
457 utf8_cp, CSM_NONE,
458 NULL, NULL, NULL);
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);
464 return bookmark;
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);
483 if (!table)
484 return 0;
486 if (url) {
487 url2 = convert_string(table, url, strlen(url),
488 utf8_cp, CSM_NONE,
489 NULL, NULL, NULL);
490 if (!url2) return 0;
491 sanitize_url(url2);
494 if (title) {
495 title2 = convert_string(table, title, strlen(title),
496 utf8_cp, CSM_NONE,
497 NULL, NULL, NULL);
498 if (!title2) {
499 mem_free_if(url2);
500 return 0;
502 sanitize_title(title2);
505 set_event_id(update_bookmark_event_id, "bookmark-update");
506 trigger_event(update_bookmark_event_id, bm, title2, url2);
508 if (title2) {
509 mem_free_set(&bm->title, title2);
512 if (url2) {
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();
530 return 1;
533 /** Search for a bookmark with the given title. The search does not
534 * recurse into subfolders.
536 * @param folder
537 * Search in this folder. NULL means search in the root.
539 * @param title
540 * Search for this title. Must be in UTF-8 and not NULL.
542 * @return The bookmark, or NULL if not found. */
543 struct bookmark *
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;
554 return NULL;
557 /* Search bookmark cache for item matching url. */
558 struct bookmark *
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))
565 return NULL;
567 /* Search for cached entry. */
569 item = get_hash_item(bookmark_cache, url, strlen(url));
571 return item ? item->value : NULL;
574 static void
575 bookmark_terminal(struct terminal *term, struct bookmark *folder)
577 unsigned char title[MAX_STR_LEN], url[MAX_STR_LEN];
578 struct window *tab;
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))
585 continue;
587 if (!get_current_title(ses, title, MAX_STR_LEN))
588 continue;
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.
597 * @param term
598 * The terminal whose open documents should be bookmarked.
600 * @param foldername
601 * The name of the new bookmark folder, in UTF-8. */
602 void
603 bookmark_terminal_tabs(struct terminal *term, unsigned char *foldername)
605 struct bookmark *folder = add_bookmark(NULL, 1, foldername, NULL);
607 if (!folder) return;
609 bookmark_terminal(term, folder);
612 static void
613 bookmark_all_terminals(struct bookmark *folder)
615 unsigned int n = 0;
616 struct terminal *term;
618 if (get_cmd_opt_bool("anonymous"))
619 return;
621 if (list_is_singleton(terminals)) {
622 bookmark_terminal(terminals.next, folder);
623 return;
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))
632 return;
634 ++n;
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);
646 unsigned char *
647 get_auto_save_bookmark_foldername_utf8(void)
649 unsigned char *foldername;
650 int from_cp, to_cp;
651 struct conv_table *convert_table;
653 foldername = get_opt_str("ui.sessions.auto_save_foldername");
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),
666 to_cp, CSM_NONE,
667 NULL, NULL, NULL);
670 void
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"))
677 return;
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);
690 static void
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");
700 #ifdef HAVE_STRFTIME
701 add_to_string(&folderstring, " - ");
702 add_date_to_string(&folderstring, get_opt_str("ui.date_format"), NULL);
703 #endif
705 /* folderstring must be in the system codepage because
706 * add_date_to_string() uses strftime(). */
707 folder = add_bookmark_cp(NULL, 1, get_cp_index("System"),
708 folderstring.source, NULL);
709 done_string(&folderstring);
710 if (!folder) return;
712 bookmark_all_terminals(folder);
714 if (bm_snapshot_last_folder) delete_bookmark(bm_snapshot_last_folder);
715 bm_snapshot_last_folder = folder;
719 /** Open all bookmarks from the named folder.
721 * @param ses
722 * The session in which to open the first bookmark. The other
723 * bookmarks of the folder open in new tabs on the same terminal.
725 * @param foldername
726 * The name of the bookmark folder, in UTF-8. */
727 void
728 open_bookmark_folder(struct session *ses, unsigned char *foldername)
730 struct bookmark *bookmark;
731 struct bookmark *folder = NULL;
732 struct bookmark *current = NULL;
734 assert(foldername && ses);
735 if_assert_failed return;
737 foreach (bookmark, bookmarks) {
738 if (bookmark->box_item->type != BI_FOLDER)
739 continue;
740 if (strcmp(bookmark->title, foldername))
741 continue;
742 folder = bookmark;
743 break;
746 if (!folder) return;
748 foreach (bookmark, folder->child) {
749 struct uri *uri;
751 if (bookmark->box_item->type == BI_FOLDER
752 || bookmark->box_item->type == BI_SEPARATOR
753 || !*bookmark->url)
754 continue;
756 /** @todo Bug 1066: Tell the URI layer that
757 * bookmark->url is UTF-8. */
758 uri = get_translated_uri(bookmark->url, NULL);
759 if (!uri) continue;
761 /* Save the first bookmark for the current tab */
762 if (!current) {
763 current = bookmark;
764 goto_uri(ses, uri);
765 } else {
766 open_uri_in_new_tab(ses, uri, 1, 0);
769 done_uri(uri);