Explicit cast to (const char *) in strcasestr for C++
[elinks.git] / src / globhist / globhist.c
blob6bf739645c00eb5d7159123d6aa231b37c43ff27
1 /* Global history */
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
5 #endif
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
11 #include "elinks.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #ifdef HAVE_TIME_H
17 #include <time.h>
18 #endif
20 #include "bfu/dialog.h"
21 #include "config/home.h"
22 #include "config/options.h"
23 #include "globhist/dialogs.h"
24 #include "globhist/globhist.h"
25 #include "intl/gettext/libintl.h"
26 #include "main/module.h"
27 #include "main/object.h"
28 #include "main/select.h"
29 #include "util/conv.h"
30 #include "util/file.h"
31 #include "util/hash.h"
32 #include "util/memory.h"
33 #include "util/secsave.h"
34 #include "util/string.h"
35 #include "util/lists.h"
36 #include "util/time.h"
38 #define GLOBAL_HISTORY_FILENAME "globhist"
41 INIT_INPUT_HISTORY(global_history);
42 INIT_LIST_OF(struct global_history_item, global_history_reap_list);
45 /* GUI stuff. Declared here because done_global_history() frees it. */
46 unsigned char *gh_last_searched_title = NULL;
47 unsigned char *gh_last_searched_url = NULL;
49 enum global_history_options {
50 GLOBHIST_TREE,
52 GLOBHIST_ENABLE,
53 GLOBHIST_MAX_ITEMS,
54 GLOBHIST_DISPLAY_TYPE,
56 GLOBHIST_OPTIONS,
59 static union option_info global_history_options[] = {
60 INIT_OPT_TREE("document.history", N_("Global history"),
61 "global", 0,
62 N_("Global history options.")),
64 INIT_OPT_BOOL("document.history.global", N_("Enable"),
65 "enable", 0, 1,
66 N_("Enable global history (\"history of all pages "
67 "visited\").")),
69 INIT_OPT_INT("document.history.global", N_("Maximum number of entries"),
70 "max_items", 0, 1, INT_MAX, 1024,
71 N_("Maximum number of entries in the global history.")),
73 INIT_OPT_INT("document.history.global", N_("Display style"),
74 "display_type", 0, 0, 1, 0,
75 N_("What to display in global history dialog:\n"
76 "0 is URLs\n"
77 "1 is page titles")),
79 /* Compatibility alias: added by jonas at 2004-07-16, 0.9.CVS. */
80 INIT_OPT_ALIAS("document.history.global", "write_interval", 0,
81 "infofiles.save_interval"),
83 NULL_OPTION_INFO,
86 #define get_opt_globhist(which) global_history_options[(which)].option.value
87 #define get_globhist_enable() get_opt_globhist(GLOBHIST_ENABLE).number
88 #define get_globhist_max_items() get_opt_globhist(GLOBHIST_MAX_ITEMS).number
89 #define get_globhist_display_type() get_opt_globhist(GLOBHIST_DISPLAY_TYPE).number
91 static struct hash *globhist_cache = NULL;
92 static int globhist_cache_entries = 0;
95 static void
96 remove_item_from_global_history(struct global_history_item *history_item)
98 del_from_history_list(&global_history, history_item);
100 if (globhist_cache) {
101 struct hash_item *item;
103 item = get_hash_item(globhist_cache, history_item->url, strlen(history_item->url));
104 if (item) {
105 del_hash_item(globhist_cache, item);
106 globhist_cache_entries--;
111 static void
112 reap_deleted_globhist_items(void)
114 struct global_history_item *history_item, *next;
116 foreachsafe(history_item, next, global_history_reap_list) {
117 if (!is_object_used(history_item)) {
118 del_from_list(history_item);
119 mem_free(history_item->title);
120 mem_free(history_item->url);
121 mem_free(history_item);
126 static void
127 done_global_history_item(struct global_history_item *history_item)
129 done_listbox_item(&globhist_browser, history_item->box_item);
131 history_item->box_item = NULL;
133 add_to_list(global_history_reap_list, history_item);
136 void
137 delete_global_history_item(struct global_history_item *history_item)
139 remove_item_from_global_history(history_item);
141 done_global_history_item(history_item);
144 /* Search global history for item matching url. */
145 struct global_history_item *
146 get_global_history_item(unsigned char *url)
148 struct hash_item *item;
150 if (!url || !globhist_cache) return NULL;
152 /* Search for cached entry. */
154 item = get_hash_item(globhist_cache, url, strlen(url));
156 return item ? (struct global_history_item *) item->value : NULL;
159 #if 0
160 /* Search global history for certain item. There must be full match with the
161 * parameter or the parameter must be NULL/zero. */
162 struct global_history_item *
163 multiget_global_history_item(unsigned char *url, unsigned char *title, time_t time)
165 struct global_history_item *history_item;
167 /* Code duplication vs performance, since this function is called most
168 * of time for url matching only... Execution time is divided by 2. */
169 if (url && !title && !time) {
170 return get_global_history_item(url);
171 } else {
172 foreach (history_item, global_history.items) {
173 if ((!url || !strcmp(history_item->url, url)) &&
174 (!title || !strcmp(history_item->title, title)) &&
175 (!time || history_item->last_visit == time)) {
176 return history_item;
181 return NULL;
183 #endif
185 static struct global_history_item *
186 init_global_history_item(unsigned char *url, unsigned char *title, time_t vtime)
188 struct global_history_item *history_item;
190 history_item = mem_calloc(1, sizeof(*history_item));
191 if (!history_item)
192 return NULL;
194 history_item->last_visit = vtime;
195 history_item->title = stracpy(empty_string_or_(title));
196 if (!history_item->title) {
197 mem_free(history_item);
198 return NULL;
200 sanitize_title(history_item->title);
202 history_item->url = stracpy(url);
203 if (!history_item->url || !sanitize_url(history_item->url)) {
204 mem_free_if(history_item->url);
205 mem_free(history_item->title);
206 mem_free(history_item);
207 return NULL;
210 history_item->box_item = add_listbox_leaf(&globhist_browser, NULL,
211 history_item);
212 if (!history_item->box_item) {
213 mem_free(history_item->url);
214 mem_free(history_item->title);
215 mem_free(history_item);
216 return NULL;
219 object_nolock(history_item, "globhist");
221 return history_item;
224 static int
225 cap_global_history(int max_globhist_items)
227 while (global_history.size >= max_globhist_items) {
228 struct global_history_item *history_item;
230 history_item = global_history.entries.prev;
232 if ((void *) history_item == &global_history.entries) {
233 INTERNAL("global history is empty");
234 global_history.size = 0;
235 return 0;
238 delete_global_history_item(history_item);
241 return 1;
244 static void
245 add_item_to_global_history(struct global_history_item *history_item,
246 int max_globhist_items)
248 add_to_history_list(&global_history, history_item);
250 /* Hash creation if needed. */
251 if (!globhist_cache)
252 globhist_cache = init_hash8();
254 if (globhist_cache && globhist_cache_entries < max_globhist_items) {
255 int urllen = strlen(history_item->url);
257 /* Create a new entry. */
258 if (add_hash_item(globhist_cache, history_item->url, urllen, history_item)) {
259 globhist_cache_entries++;
264 /* Add a new entry in history list, take care of duplicate, respect history
265 * size limit, and update any open history dialogs. */
266 void
267 add_global_history_item(unsigned char *url, unsigned char *title, time_t vtime)
269 struct global_history_item *history_item;
270 int max_globhist_items;
272 if (!url || !get_globhist_enable()) return;
274 max_globhist_items = get_globhist_max_items();
276 history_item = get_global_history_item(url);
277 if (history_item) delete_global_history_item(history_item);
279 if (!cap_global_history(max_globhist_items)) return;
281 reap_deleted_globhist_items();
283 history_item = init_global_history_item(url, title, vtime);
284 if (!history_item) return;
286 add_item_to_global_history(history_item, max_globhist_items);
291 globhist_simple_search(unsigned char *search_url, unsigned char *search_title)
293 struct global_history_item *history_item;
295 if (!search_title || !search_url)
296 return 0;
298 /* Memorize last searched title */
299 mem_free_set(&gh_last_searched_title, stracpy(search_title));
300 if (!gh_last_searched_title) return 0;
302 /* Memorize last searched url */
303 mem_free_set(&gh_last_searched_url, stracpy(search_url));
304 if (!gh_last_searched_url) return 0;
306 if (!*search_title && !*search_url) {
307 /* No search terms, make all entries visible. */
308 foreach (history_item, global_history.entries) {
309 history_item->box_item->visible = 1;
311 return 1;
314 foreach (history_item, global_history.entries) {
315 /* Make matching entries visible, hide others. */
316 if ((*search_title
317 && strcasestr((const char *)history_item->title, (const char *)search_title))
318 || (*search_url
319 && c_strcasestr((const char *)history_item->url, (const char *)search_url))) {
320 history_item->box_item->visible = 1;
321 } else {
322 history_item->box_item->visible = 0;
325 return 1;
329 static void
330 read_global_history(void)
332 unsigned char in_buffer[MAX_STR_LEN * 3];
333 unsigned char *file_name = GLOBAL_HISTORY_FILENAME;
334 unsigned char *title;
335 FILE *f;
337 if (!get_globhist_enable()
338 || get_cmd_opt_bool("anonymous"))
339 return;
341 if (elinks_home) {
342 file_name = straconcat(elinks_home, file_name,
343 (unsigned char *) NULL);
344 if (!file_name) return;
346 f = fopen(file_name, "rb");
347 if (elinks_home) mem_free(file_name);
348 if (!f) return;
350 title = in_buffer;
351 global_history.nosave = 1;
353 while (fgets(in_buffer, sizeof(in_buffer), f)) {
354 unsigned char *url, *last_visit, *eol;
356 url = strchr((const char *)title, '\t');
357 if (!url) continue;
358 *url++ = '\0'; /* Now url points to the character after \t. */
360 last_visit = strchr((const char *)url, '\t');
361 if (!last_visit) continue;
362 *last_visit++ = '\0';
364 eol = strchr((const char *)last_visit, '\n');
365 if (!eol) continue;
366 *eol = '\0'; /* Drop ending '\n'. */
368 add_global_history_item(url, title, str_to_time_t(last_visit));
371 global_history.nosave = 0;
372 fclose(f);
375 static void
376 write_global_history(void)
378 struct global_history_item *history_item;
379 unsigned char *file_name;
380 struct secure_save_info *ssi;
382 if (!global_history.dirty || !elinks_home
383 || !get_globhist_enable()
384 || get_cmd_opt_bool("anonymous"))
385 return;
387 file_name = straconcat(elinks_home, GLOBAL_HISTORY_FILENAME,
388 (unsigned char *) NULL);
389 if (!file_name) return;
391 ssi = secure_open(file_name);
392 mem_free(file_name);
393 if (!ssi) return;
395 foreachback (history_item, global_history.entries) {
396 if (secure_fprintf(ssi, "%s\t%s\t%"TIME_PRINT_FORMAT"\n",
397 history_item->title,
398 history_item->url,
399 (time_print_T) history_item->last_visit) < 0)
400 break;
403 if (!secure_close(ssi)) global_history.dirty = 0;
406 static void
407 free_global_history(void)
409 if (globhist_cache) {
410 free_hash(&globhist_cache);
411 globhist_cache_entries = 0;
414 while (!list_empty(global_history.entries))
415 delete_global_history_item(global_history.entries.next);
417 reap_deleted_globhist_items();
420 static enum evhook_status
421 global_history_write_hook(va_list ap, void *data)
423 write_global_history();
424 return EVENT_HOOK_STATUS_NEXT;
427 struct event_hook_info global_history_hooks[] = {
428 { "periodic-saving", 0, global_history_write_hook, NULL },
430 NULL_EVENT_HOOK_INFO,
433 static void
434 init_global_history(struct module *module)
436 read_global_history();
439 static void
440 done_global_history(struct module *module)
442 write_global_history();
443 free_global_history();
444 mem_free_if(gh_last_searched_title);
445 mem_free_if(gh_last_searched_url);
448 struct module global_history_module = struct_module(
449 /* name: */ N_("Global History"),
450 /* options: */ global_history_options,
451 /* events: */ global_history_hooks,
452 /* submodules: */ NULL,
453 /* data: */ NULL,
454 /* init: */ init_global_history,
455 /* done: */ done_global_history