4 #define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
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
{
54 GLOBHIST_DISPLAY_TYPE
,
59 static struct option_info global_history_options
[] = {
60 INIT_OPT_TREE("document.history", N_("Global history"),
62 N_("Global history options.")),
64 INIT_OPT_BOOL("document.history.global", N_("Enable"),
66 N_("Enable global history (\"history of all pages "
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"
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"),
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;
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
));
105 del_hash_item(globhist_cache
, item
);
106 globhist_cache_entries
--;
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
);
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
);
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
;
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
);
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
)) {
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
));
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
);
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
);
210 history_item
->box_item
= add_listbox_leaf(&globhist_browser
, NULL
,
212 if (!history_item
->box_item
) {
213 mem_free(history_item
->url
);
214 mem_free(history_item
->title
);
215 mem_free(history_item
);
219 object_nolock(history_item
, "globhist");
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;
238 delete_global_history_item(history_item
);
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. */
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. */
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
)
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;
314 foreach (history_item
, global_history
.entries
) {
315 /* Make matching entries visible, hide others. */
317 && strcasestr(history_item
->title
, search_title
))
319 && c_strcasestr(history_item
->url
, search_url
))) {
320 history_item
->box_item
->visible
= 1;
322 history_item
->box_item
->visible
= 0;
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
;
337 if (!get_globhist_enable()
338 || get_cmd_opt_bool("anonymous"))
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
);
351 global_history
.nosave
= 1;
353 while (fgets(in_buffer
, sizeof(in_buffer
), f
)) {
354 unsigned char *url
, *last_visit
, *eol
;
356 url
= strchr(title
, '\t');
358 *url
++ = '\0'; /* Now url points to the character after \t. */
360 last_visit
= strchr(url
, '\t');
361 if (!last_visit
) continue;
362 *last_visit
++ = '\0';
364 eol
= strchr(last_visit
, '\n');
366 *eol
= '\0'; /* Drop ending '\n'. */
368 add_global_history_item(url
, title
, str_to_time_t(last_visit
));
371 global_history
.nosave
= 0;
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"))
387 file_name
= straconcat(elinks_home
, GLOBAL_HISTORY_FILENAME
,
388 (unsigned char *) NULL
);
389 if (!file_name
) return;
391 ssi
= secure_open(file_name
);
395 foreachback (history_item
, global_history
.entries
) {
396 if (secure_fprintf(ssi
, "%s\t%s\t%"TIME_PRINT_FORMAT
"\n",
399 (time_print_T
) history_item
->last_visit
) < 0)
403 if (!secure_close(ssi
)) global_history
.dirty
= 0;
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
,
434 init_global_history(struct module
*module
)
436 read_global_history();
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
,
454 /* init: */ init_global_history
,
455 /* done: */ done_global_history