1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-26 01:15:37 +00:00
elinks/src/bookmarks/bookmarks.c

773 lines
18 KiB
C
Raw Normal View History

/* Internal bookmarks support */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "bfu/dialog.h"
#include "bfu/hierbox.h"
#include "bfu/listbox.h"
#include "bookmarks/backend/common.h"
#include "bookmarks/bookmarks.h"
#include "bookmarks/dialogs.h"
#include "config/home.h"
#include "config/options.h"
#include "intl/libintl.h"
#include "main/module.h"
#include "main/object.h"
#include "protocol/uri.h"
#include "session/task.h"
#include "terminal/tab.h"
#include "util/conv.h"
#include "util/hash.h"
#include "util/lists.h"
#include "util/memory.h"
#include "util/secsave.h"
#include "util/string.h"
/* The list of bookmarks */
2007-07-26 19:39:08 +00:00
INIT_LIST_OF(struct bookmark, bookmarks);
/* Set to 1, if bookmarks have changed. */
static int bookmarks_dirty = 0;
static struct hash *bookmark_cache = NULL;
static struct bookmark *bm_snapshot_last_folder;
/* Life functions */
bug 764: Initialize the right member of union option_value INIT_OPTION used to initialize union option_value at compile time by casting the default value to LIST_OF(struct option) *, which is the type of the first member. On sparc64 and other big-endian systems where sizeof(int) < sizeof(struct list_head *), this tended to leave option->value.number as zero, thus messing up OPT_INT and OPT_BOOL at least. OPT_LONG however tended to work right. This would be easy to fix with C99 designated initializers, but doc/hacking.txt says ELinks must be kept C89 compatible. Another solution would be to make register_options() read the value from option->value.tree (the first member), cast it back to the right type, and write it to the appropriate member; but that would still require somewhat dubious conversions between integers, data pointers, and function pointers. So here's a rather more invasive solution. Add struct option_init, which is somewhat similar to struct option but has non-overlapping members for different types of values, to ensure nothing is lost in compile-time conversions. Move unsigned char *path from struct option_info to struct option_init, and replace struct option_info with a union that contains struct option_init and struct option. Now, this union can be initialized with no portability problems, and register_options() then moves the values from struct option_init to their final places in struct option. In my x86 ELinks build with plenty of options configured in, this change bloated the text section by 340 bytes but compressed the data section by 2784 bytes, presumably because union option_info is a pointer smaller than struct option_info was. (cherry picked from elinks-0.12 commit e5f6592ee20780a61f70feeb1f9e17631b9c5835) Conflicts: src/protocol/fsp/fsp.c: All options had been removed in 0.13.GIT. src/protocol/smb/smb2.c: Ditto.
2009-08-15 19:39:07 +00:00
static union option_info bookmark_options_info[] = {
INIT_OPT_TREE("", N_("Bookmarks"),
"bookmarks", OPT_ZERO,
N_("Bookmark options.")),
#ifdef CONFIG_XBEL_BOOKMARKS
INIT_OPT_INT("bookmarks", N_("File format"),
"file_format", OPT_ZERO, 0, 1, 0,
N_("File format for bookmarks (affects both reading and "
"saving):\n"
"0 is the default native ELinks format\n"
"1 is XBEL universal XML bookmarks format")),
#else
INIT_OPT_INT("bookmarks", N_("File format"),
"file_format", OPT_ZERO, 0, 1, 0,
N_("File format for bookmarks (affects both reading and "
"saving):\n"
"0 is the default native ELinks format\n"
"1 is XBEL universal XML bookmarks format (DISABLED)")),
#endif
INIT_OPT_BOOL("bookmarks", N_("Save folder state"),
"folder_state", OPT_ZERO, 1,
N_("When saving bookmarks also store whether folders are "
"expanded or not, so the look of the bookmark dialog is "
"kept across ELinks sessions. If disabled all folders will "
"appear unexpanded next time ELinks is run.")),
INIT_OPT_BOOL("ui.sessions", N_("Periodic snapshotting"),
"snapshot", OPT_ZERO, 0,
N_("Automatically save a snapshot of all tabs periodically. "
"This will periodically bookmark the tabs of each terminal "
"in a separate folder for recovery after a crash.\n"
"\n"
"This feature requires bookmark support.")),
NULL_OPTION_INFO
};
static enum evhook_status bookmark_change_hook(va_list ap, void *data);
static enum evhook_status bookmark_write_hook(va_list ap, void *data);
struct event_hook_info bookmark_hooks[] = {
2022-03-03 14:25:45 +00:00
{ "bookmark-delete", 0, bookmark_change_hook, {NULL} },
{ "bookmark-move", 0, bookmark_change_hook, {NULL} },
{ "bookmark-update", 0, bookmark_change_hook, {NULL} },
{ "periodic-saving", 0, bookmark_write_hook, {NULL} },
NULL_EVENT_HOOK_INFO,
};
static enum evhook_status
bookmark_change_hook(va_list ap, void *data)
{
struct bookmark *bookmark = va_arg(ap, struct bookmark *);
if (bookmark == bm_snapshot_last_folder)
bm_snapshot_last_folder = NULL;
return EVENT_HOOK_STATUS_NEXT;
}
static void bookmark_snapshot();
static enum evhook_status
bookmark_write_hook(va_list ap, void *data)
{
if (get_opt_bool("ui.sessions.snapshot", NULL)
&& !get_cmd_opt_bool("anonymous"))
bookmark_snapshot();
write_bookmarks();
return EVENT_HOOK_STATUS_NEXT;
}
static int
change_hook_folder_state(struct session *ses, struct option *current,
struct option *changed)
{
if (!changed->value.number) {
/* We are to collapse all folders on exit; mark bookmarks dirty
* to ensure that this will happen. */
bookmarks_set_dirty();
}
return 0;
}
static void
init_bookmarks(struct module *module)
{
static const struct change_hook_info bookmarks_change_hooks[] = {
2022-03-03 14:25:45 +00:00
{ "bookmarks.folder_state", change_hook_folder_state },
{ NULL, NULL },
};
register_change_hooks(bookmarks_change_hooks);
read_bookmarks();
}
/* Clears the bookmark list */
static void
2007-07-26 19:39:08 +00:00
free_bookmarks(LIST_OF(struct bookmark) *bookmarks_list,
LIST_OF(struct listbox_item) *box_items)
{
struct bookmark *bm;
foreach (bm, *bookmarks_list) {
if (!list_empty(bm->child))
free_bookmarks(&bm->child, &bm->box_item->child);
mem_free(bm->title);
mem_free(bm->url);
}
free_list(*box_items);
free_list(*bookmarks_list);
if (bookmark_cache) free_hash(&bookmark_cache);
}
/* Does final cleanup and saving of bookmarks */
static void
done_bookmarks(struct module *module)
{
/* This is a clean shutdown, so delete the last snapshot. */
if (bm_snapshot_last_folder) delete_bookmark(bm_snapshot_last_folder);
bm_snapshot_last_folder = NULL;
write_bookmarks();
free_bookmarks(&bookmarks, &bookmark_browser.root.child);
free_last_searched_bookmark();
}
struct module bookmarks_module = struct_module(
/* name: */ N_("Bookmarks"),
/* options: */ bookmark_options_info,
/* hooks: */ bookmark_hooks,
/* submodules: */ NULL,
/* data: */ NULL,
/* init: */ init_bookmarks,
/* done: */ done_bookmarks
);
/* Read/write wrappers */
/* Loads the bookmarks from file */
void
read_bookmarks(void)
{
bookmarks_read();
}
void
write_bookmarks(void)
{
if (get_cmd_opt_bool("anonymous")) {
bookmarks_unset_dirty();
return;
}
bookmarks_write(&bookmarks);
}
/* Bookmarks manipulation */
void
bookmarks_set_dirty(void)
{
bookmarks_dirty = 1;
}
void
bookmarks_unset_dirty(void)
{
bookmarks_dirty = 0;
}
int
bookmarks_are_dirty(void)
{
return (bookmarks_dirty == 1);
}
#define check_bookmark_cache(url) (bookmark_cache && (url) && *(url))
static void
done_bookmark(struct bookmark *bm)
{
done_listbox_item(&bookmark_browser, bm->box_item);
mem_free(bm->title);
mem_free(bm->url);
mem_free(bm);
}
void
delete_bookmark(struct bookmark *bm)
{
static int delete_bookmark_event_id = EVENT_NONE;
while (!list_empty(bm->child)) {
2022-01-24 18:29:32 +00:00
delete_bookmark((struct bookmark *)bm->child.next);
}
if (check_bookmark_cache(bm->url)) {
struct hash_item *item;
item = get_hash_item(bookmark_cache, bm->url, strlen(bm->url));
if (item) del_hash_item(bookmark_cache, item);
}
set_event_id(delete_bookmark_event_id, "bookmark-delete");
trigger_event(delete_bookmark_event_id, bm);
del_from_list(bm);
bookmarks_set_dirty();
done_bookmark(bm);
}
/** Deletes any bookmarks with no URLs (i.e., folders) and of which
* the title matches the given argument.
*
* @param foldername
* The title of the folder, in UTF-8. */
static void
delete_folder_by_name(const char *foldername)
{
struct bookmark *bookmark, *next;
foreachsafe (bookmark, next, bookmarks) {
if ((bookmark->url && *bookmark->url)
|| strcmp(bookmark->title, foldername))
continue;
delete_bookmark(bookmark);
}
}
/** Allocate and initialize a bookmark in the given folder. This
* however does not set bookmark.box_item; use add_bookmark() for
* that.
*
* @param root
* The folder in which to add the bookmark, or NULL to add it at
* top level.
* @param title
* Title of the bookmark. Must be in UTF-8 and not NULL.
* "-" means add a separator.
* @param url
* URL to which the bookmark will point. Must be in UTF-8.
* NULL or "" means add a bookmark folder.
*
* @return the new bookmark, or NULL on error. */
static struct bookmark *
2022-01-31 14:38:17 +00:00
init_bookmark(struct bookmark *root, const char *title, const char *url)
{
struct bookmark *bm;
2022-01-16 20:08:50 +00:00
bm = (struct bookmark *)mem_calloc(1, sizeof(*bm));
if (!bm) return NULL;
bm->title = null_or_stracpy(title);
if (!bm->title) {
mem_free(bm);
return NULL;
}
sanitize_title(bm->title);
bm->url = stracpy(empty_string_or_(url));
if (!bm->url) {
mem_free(bm->title);
mem_free(bm);
return NULL;
}
sanitize_url(bm->url);
bm->root = root;
init_list(bm->child);
object_nolock(bm, "bookmark");
return bm;
}
static void
add_bookmark_item_to_bookmarks(struct bookmark *bm, struct bookmark *root, int place)
{
/* Actually add it */
if (place) {
if (root)
add_to_list_end(root->child, bm);
else
add_to_list_end(bookmarks, bm);
} else {
if (root)
add_to_list(root->child, bm);
else
add_to_list(bookmarks, bm);
}
bookmarks_set_dirty();
/* Hash creation if needed. */
if (!bookmark_cache)
bookmark_cache = init_hash8();
/* Create a new entry. */
if (check_bookmark_cache(bm->url))
add_hash_item(bookmark_cache, bm->url, strlen(bm->url), bm);
}
/** Add a bookmark to the bookmark list.
*
* @param root
* The folder in which to add the bookmark, or NULL to add it at
* top level.
* @param place
* 0 means add to the top. 1 means add to the bottom.
* @param title
* Title of the bookmark. Must be in UTF-8 and not NULL.
* "-" means add a separator.
* @param url
* URL to which the bookmark will point. Must be in UTF-8.
* NULL or "" means add a bookmark folder.
*
2008-10-19 22:08:20 +00:00
* @return the new bookmark, or NULL on error.
*
* @see add_bookmark_cp() */
struct bookmark *
2022-01-31 14:38:17 +00:00
add_bookmark(struct bookmark *root, int place, const char *title,
const char *url)
{
enum listbox_item_type type;
struct bookmark *bm;
bm = init_bookmark(root, title, url);
if (!bm) return NULL;
if (url && *url) {
type = BI_LEAF;
} else if (title && title[0] == '-' && title[1] == '\0') {
type = BI_SEPARATOR;
} else {
type = BI_FOLDER;
}
bm->box_item = add_listbox_item(&bookmark_browser,
root ? root->box_item : NULL,
type,
(void *) bm,
place ? -1 : 1);
if (!bm->box_item) {
mem_free(bm->url);
mem_free(bm->title);
mem_free(bm);
return NULL;
}
add_bookmark_item_to_bookmarks(bm, root, place);
return bm;
}
2008-10-19 22:08:20 +00:00
/** Add a bookmark to the bookmark list.
*
* @param root
* The folder in which to add the bookmark, or NULL to add it at
* top level.
* @param place
* 0 means add to the top. 1 means add to the bottom.
* @param codepage
* Codepage of @a title and @a url.
* @param title
* Title of the bookmark. Must not be NULL.
* "-" means add a separator.
* @param url
* URL to which the bookmark will point.
* NULL or "" means add a bookmark folder.
*
* @return the new bookmark.
*
* @see add_bookmark() */
struct bookmark *
add_bookmark_cp(struct bookmark *root, int place, int codepage,
const char *title, const char *url)
2008-10-19 22:08:20 +00:00
{
const int utf8_cp = get_cp_index("UTF-8");
struct conv_table *table;
char *utf8_title = NULL;
char *utf8_url = NULL;
2008-10-19 22:08:20 +00:00
struct bookmark *bookmark = NULL;
if (!url)
url = "";
table = get_translation_table(codepage, utf8_cp);
if (!table)
return NULL;
utf8_title = convert_string(table, title, strlen(title),
utf8_cp, CSM_NONE,
NULL, NULL, NULL);
utf8_url = convert_string(table, url, strlen(url),
utf8_cp, CSM_NONE,
NULL, NULL, NULL);
if (utf8_title && utf8_url)
bookmark = add_bookmark(root, place,
utf8_title, utf8_url);
mem_free_if(utf8_title);
mem_free_if(utf8_url);
return bookmark;
}
/* Updates an existing bookmark.
*
* If there's any problem, return 0. Otherwise, return 1.
*
* If any of the fields are NULL, the value is left unchanged. */
int
update_bookmark(struct bookmark *bm, int codepage,
char *title, char *url)
{
static int update_bookmark_event_id = EVENT_NONE;
const int utf8_cp = get_cp_index("UTF-8");
struct conv_table *table;
char *title2 = NULL;
char *url2 = NULL;
table = get_translation_table(codepage, utf8_cp);
if (!table)
return 0;
if (url) {
url2 = convert_string(table, url, strlen(url),
utf8_cp, CSM_NONE,
NULL, NULL, NULL);
if (!url2) return 0;
sanitize_url(url2);
}
if (title) {
title2 = convert_string(table, title, strlen(title),
utf8_cp, CSM_NONE,
NULL, NULL, NULL);
if (!title2) {
mem_free_if(url2);
return 0;
}
sanitize_title(title2);
}
set_event_id(update_bookmark_event_id, "bookmark-update");
trigger_event(update_bookmark_event_id, bm, title2, url2);
if (title2) {
2006-06-06 03:06:47 +00:00
mem_free_set(&bm->title, title2);
}
if (url2) {
if (check_bookmark_cache(bm->url)) {
struct hash_item *item;
int len = strlen(bm->url);
item = get_hash_item(bookmark_cache, bm->url, len);
if (item) del_hash_item(bookmark_cache, item);
}
if (check_bookmark_cache(url2)) {
add_hash_item(bookmark_cache, url2, strlen(url2), bm);
}
2006-06-06 03:06:47 +00:00
mem_free_set(&bm->url, url2);
}
bookmarks_set_dirty();
return 1;
}
/** Search for a bookmark with the given title. The search does not
* recurse into subfolders.
*
* @param folder
* Search in this folder. NULL means search in the root.
*
* @param title
* Search for this title. Must be in UTF-8 and not NULL.
*
* @return The bookmark, or NULL if not found. */
struct bookmark *
get_bookmark_by_name(struct bookmark *folder, char *title)
{
struct bookmark *bookmark;
2007-07-26 19:39:08 +00:00
LIST_OF(struct bookmark) *lh;
lh = folder ? &folder->child : &bookmarks;
foreach (bookmark, *lh)
if (!strcmp(bookmark->title, title)) return bookmark;
return NULL;
}
/* Search bookmark cache for item matching url. */
struct bookmark *
get_bookmark(char *url)
{
struct hash_item *item;
/** @todo Bug 1066: URLs in bookmark_cache should be UTF-8 */
if (!check_bookmark_cache(url))
return NULL;
/* Search for cached entry. */
item = get_hash_item(bookmark_cache, url, strlen(url));
2022-01-24 18:29:32 +00:00
return (struct bookmark *)(item ? item->value : NULL);
}
static void
bookmark_terminal(struct terminal *term, struct bookmark *folder)
{
char title[MAX_STR_LEN], url[MAX_STR_LEN];
struct window *tab;
int term_cp = get_terminal_codepage(term);
foreachback_tab (tab, term->windows) {
2022-01-24 18:29:32 +00:00
struct session *ses = (struct session *)tab->data;
if (!get_current_url(ses, url, MAX_STR_LEN))
continue;
if (!get_current_title(ses, title, MAX_STR_LEN))
continue;
add_bookmark_cp(folder, 1, term_cp, title, url);
}
}
/** Create a bookmark for each document on the specified terminal,
* and a folder to contain those bookmarks.
*
* @param term
* The terminal whose open documents should be bookmarked.
*
* @param foldername
* The name of the new bookmark folder, in UTF-8. */
void
bookmark_terminal_tabs(struct terminal *term, char *foldername)
{
struct bookmark *folder = add_bookmark(NULL, 1, foldername, NULL);
if (!folder) return;
bookmark_terminal(term, folder);
}
static void
bookmark_all_terminals(struct bookmark *folder)
{
unsigned int n = 0;
struct terminal *term;
if (get_cmd_opt_bool("anonymous"))
return;
if (list_is_singleton(terminals)) {
2022-01-24 18:29:32 +00:00
bookmark_terminal((struct terminal *)terminals.next, folder);
return;
}
foreach (term, terminals) {
char subfoldername[5];
struct bookmark *subfolder;
if (ulongcat(subfoldername, NULL, n, sizeof(subfoldername)-1, 0)
>= sizeof(subfoldername)-1)
return;
++n;
/* Because subfoldername[] contains only digits,
* it is OK as UTF-8. */
subfolder = add_bookmark(folder, 1, subfoldername, NULL);
if (!subfolder) return;
bookmark_terminal(term, subfolder);
}
}
char *
get_auto_save_bookmark_foldername_utf8(void)
{
char *foldername;
int from_cp, to_cp;
struct conv_table *convert_table;
foldername = get_opt_str("ui.sessions.auto_save_foldername", NULL);
if (!*foldername) return NULL;
/* The charset of the string returned by get_opt_str()
* seems to be documented nowhere. Let's assume it is
* the system charset. */
from_cp = get_cp_index("System");
to_cp = get_cp_index("UTF-8");
convert_table = get_translation_table(from_cp, to_cp);
if (!convert_table) return NULL;
return convert_string(convert_table,
foldername, strlen(foldername),
to_cp, CSM_NONE,
NULL, NULL, NULL);
}
void
bookmark_auto_save_tabs(struct terminal *term)
{
char *foldername; /* UTF-8 */
if (get_cmd_opt_bool("anonymous")
|| !get_opt_bool("ui.sessions.auto_save", NULL))
return;
foldername = get_auto_save_bookmark_foldername_utf8();
if (!foldername) return;
/* Ensure uniqueness of the auto save folder, so it is possible to
* restore the (correct) session when starting up. */
delete_folder_by_name(foldername);
bookmark_terminal_tabs(term, foldername);
mem_free(foldername);
}
static void
2005-11-25 17:51:21 +00:00
bookmark_snapshot(void)
{
struct string folderstring;
struct bookmark *folder;
if (!init_string(&folderstring)) return;
add_to_string(&folderstring, "Session snapshot");
#ifdef HAVE_STRFTIME
add_to_string(&folderstring, " - ");
add_date_to_string(&folderstring, get_opt_str("ui.date_format", NULL),
NULL);
#endif
/* folderstring must be in the system codepage because
* add_date_to_string() uses strftime(). */
folder = add_bookmark_cp(NULL, 1, get_cp_index("System"),
folderstring.source, NULL);
done_string(&folderstring);
if (!folder) return;
bookmark_all_terminals(folder);
if (bm_snapshot_last_folder) delete_bookmark(bm_snapshot_last_folder);
bm_snapshot_last_folder = folder;
}
/** Open all bookmarks from the named folder.
*
* @param ses
* The session in which to open the first bookmark. The other
* bookmarks of the folder open in new tabs on the same terminal.
*
* @param foldername
* The name of the bookmark folder, in UTF-8. */
void
open_bookmark_folder(struct session *ses, char *foldername)
{
struct bookmark *bookmark;
struct bookmark *folder = NULL;
struct bookmark *current = NULL;
assert(foldername && ses);
if_assert_failed return;
foreach (bookmark, bookmarks) {
if (bookmark->box_item->type != BI_FOLDER)
continue;
if (strcmp(bookmark->title, foldername))
continue;
folder = bookmark;
break;
}
if (!folder) return;
foreach (bookmark, folder->child) {
struct uri *uri;
if (bookmark->box_item->type == BI_FOLDER
|| bookmark->box_item->type == BI_SEPARATOR
|| !*bookmark->url)
continue;
/** @todo Bug 1066: Tell the URI layer that
* bookmark->url is UTF-8. */
uri = get_translated_uri(bookmark->url, NULL);
if (!uri) continue;
/* Save the first bookmark for the current tab */
if (!current) {
current = bookmark;
goto_uri(ses, uri);
} else {
open_uri_in_new_tab(ses, uri, 1, 0);
}
done_uri(uri);
}
}