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 visited\").")),
68 INIT_OPT_INT("document.history.global", N_("Maximum number of entries"),
69 "max_items", 0, 1, INT_MAX
, 1024,
70 N_("Maximum number of entries in the global history.")),
72 INIT_OPT_INT("document.history.global", N_("Display style"),
73 "display_type", 0, 0, 1, 0,
74 N_("What to display in global history dialog:\n"
78 /* Compatibility alias: added by jonas at 2004-07-16, 0.9.CVS. */
79 INIT_OPT_ALIAS("document.history.global", "write_interval", 0,
80 "infofiles.save_interval"),
85 #define get_opt_globhist(which) global_history_options[(which)].option.value
86 #define get_globhist_enable() get_opt_globhist(GLOBHIST_ENABLE).number
87 #define get_globhist_max_items() get_opt_globhist(GLOBHIST_MAX_ITEMS).number
88 #define get_globhist_display_type() get_opt_globhist(GLOBHIST_DISPLAY_TYPE).number
90 static struct hash
*globhist_cache
= NULL
;
91 static int globhist_cache_entries
= 0;
95 remove_item_from_global_history(struct global_history_item
*history_item
)
97 del_from_history_list(&global_history
, history_item
);
100 struct hash_item
*item
;
102 item
= get_hash_item(globhist_cache
, history_item
->url
, strlen(history_item
->url
));
104 del_hash_item(globhist_cache
, item
);
105 globhist_cache_entries
--;
111 reap_deleted_globhist_items(void)
113 struct global_history_item
*history_item
, *next
;
115 foreachsafe(history_item
, next
, global_history_reap_list
) {
116 if (!is_object_used(history_item
)) {
117 del_from_list(history_item
);
118 mem_free(history_item
->title
);
119 mem_free(history_item
->url
);
120 mem_free(history_item
);
126 done_global_history_item(struct global_history_item
*history_item
)
128 done_listbox_item(&globhist_browser
, history_item
->box_item
);
130 history_item
->box_item
= NULL
;
132 add_to_list(global_history_reap_list
, history_item
);
136 delete_global_history_item(struct global_history_item
*history_item
)
138 remove_item_from_global_history(history_item
);
140 done_global_history_item(history_item
);
143 /* Search global history for item matching url. */
144 struct global_history_item
*
145 get_global_history_item(unsigned char *url
)
147 struct hash_item
*item
;
149 if (!url
|| !globhist_cache
) return NULL
;
151 /* Search for cached entry. */
153 item
= get_hash_item(globhist_cache
, url
, strlen(url
));
155 return item
? (struct global_history_item
*) item
->value
: NULL
;
159 /* Search global history for certain item. There must be full match with the
160 * parameter or the parameter must be NULL/zero. */
161 struct global_history_item
*
162 multiget_global_history_item(unsigned char *url
, unsigned char *title
, time_t time
)
164 struct global_history_item
*history_item
;
166 /* Code duplication vs performance, since this function is called most
167 * of time for url matching only... Execution time is divided by 2. */
168 if (url
&& !title
&& !time
) {
169 return get_global_history_item(url
);
171 foreach (history_item
, global_history
.items
) {
172 if ((!url
|| !strcmp(history_item
->url
, url
)) &&
173 (!title
|| !strcmp(history_item
->title
, title
)) &&
174 (!time
|| history_item
->last_visit
== time
)) {
184 static struct global_history_item
*
185 init_global_history_item(unsigned char *url
, unsigned char *title
, time_t vtime
)
187 struct global_history_item
*history_item
;
189 history_item
= mem_calloc(1, sizeof(*history_item
));
193 history_item
->last_visit
= vtime
;
194 history_item
->title
= stracpy(empty_string_or_(title
));
195 if (!history_item
->title
) {
196 mem_free(history_item
);
199 sanitize_title(history_item
->title
);
201 history_item
->url
= stracpy(url
);
202 if (!history_item
->url
|| !sanitize_url(history_item
->url
)) {
203 mem_free_if(history_item
->url
);
204 mem_free(history_item
->title
);
205 mem_free(history_item
);
209 history_item
->box_item
= add_listbox_leaf(&globhist_browser
, NULL
,
211 if (!history_item
->box_item
) {
212 mem_free(history_item
->url
);
213 mem_free(history_item
->title
);
214 mem_free(history_item
);
218 object_nolock(history_item
, "globhist");
224 cap_global_history(int max_globhist_items
)
226 while (global_history
.size
>= max_globhist_items
) {
227 struct global_history_item
*history_item
;
229 history_item
= global_history
.entries
.prev
;
231 if ((void *) history_item
== &global_history
.entries
) {
232 INTERNAL("global history is empty");
233 global_history
.size
= 0;
237 delete_global_history_item(history_item
);
244 add_item_to_global_history(struct global_history_item
*history_item
,
245 int max_globhist_items
)
247 add_to_history_list(&global_history
, history_item
);
249 /* Hash creation if needed. */
251 globhist_cache
= init_hash8();
253 if (globhist_cache
&& globhist_cache_entries
< max_globhist_items
) {
254 int urllen
= strlen(history_item
->url
);
256 /* Create a new entry. */
257 if (add_hash_item(globhist_cache
, history_item
->url
, urllen
, history_item
)) {
258 globhist_cache_entries
++;
263 /* Add a new entry in history list, take care of duplicate, respect history
264 * size limit, and update any open history dialogs. */
266 add_global_history_item(unsigned char *url
, unsigned char *title
, time_t vtime
)
268 struct global_history_item
*history_item
;
269 int max_globhist_items
;
271 if (!url
|| !get_globhist_enable()) return;
273 max_globhist_items
= get_globhist_max_items();
275 history_item
= get_global_history_item(url
);
276 if (history_item
) delete_global_history_item(history_item
);
278 if (!cap_global_history(max_globhist_items
)) return;
280 reap_deleted_globhist_items();
282 history_item
= init_global_history_item(url
, title
, vtime
);
283 if (!history_item
) return;
285 add_item_to_global_history(history_item
, max_globhist_items
);
290 globhist_simple_search(unsigned char *search_url
, unsigned char *search_title
)
292 struct global_history_item
*history_item
;
294 if (!search_title
|| !search_url
)
297 /* Memorize last searched title */
298 mem_free_set(&gh_last_searched_title
, stracpy(search_title
));
299 if (!gh_last_searched_title
) return 0;
301 /* Memorize last searched url */
302 mem_free_set(&gh_last_searched_url
, stracpy(search_url
));
303 if (!gh_last_searched_url
) {
304 mem_free(gh_last_searched_title
);
308 if (!*search_title
&& !*search_url
) {
309 /* No search terms, make all entries visible. */
310 foreach (history_item
, global_history
.entries
) {
311 history_item
->box_item
->visible
= 1;
316 foreach (history_item
, global_history
.entries
) {
317 /* Make matching entries visible, hide others. */
319 && strcasestr(history_item
->title
, search_title
))
321 && strcasestr(history_item
->url
, search_url
))) {
322 history_item
->box_item
->visible
= 1;
324 history_item
->box_item
->visible
= 0;
332 read_global_history(void)
334 unsigned char in_buffer
[MAX_STR_LEN
* 3];
335 unsigned char *file_name
= GLOBAL_HISTORY_FILENAME
;
336 unsigned char *title
;
339 if (!get_globhist_enable()
340 || get_cmd_opt_bool("anonymous"))
344 file_name
= straconcat(elinks_home
, file_name
,
345 (unsigned char *) NULL
);
346 if (!file_name
) return;
348 f
= fopen(file_name
, "rb");
349 if (elinks_home
) mem_free(file_name
);
353 global_history
.nosave
= 1;
355 while (fgets(in_buffer
, sizeof(in_buffer
), f
)) {
356 unsigned char *url
, *last_visit
, *eol
;
358 url
= strchr(title
, '\t');
360 *url
++ = '\0'; /* Now url points to the character after \t. */
362 last_visit
= strchr(url
, '\t');
363 if (!last_visit
) continue;
364 *last_visit
++ = '\0';
366 eol
= strchr(last_visit
, '\n');
368 *eol
= '\0'; /* Drop ending '\n'. */
370 add_global_history_item(url
, title
, str_to_time_t(last_visit
));
373 global_history
.nosave
= 0;
378 write_global_history(void)
380 struct global_history_item
*history_item
;
381 unsigned char *file_name
;
382 struct secure_save_info
*ssi
;
384 if (!global_history
.dirty
|| !elinks_home
385 || !get_globhist_enable()
386 || get_cmd_opt_bool("anonymous"))
389 file_name
= straconcat(elinks_home
, GLOBAL_HISTORY_FILENAME
,
390 (unsigned char *) NULL
);
391 if (!file_name
) return;
393 ssi
= secure_open(file_name
);
397 foreachback (history_item
, global_history
.entries
) {
398 if (secure_fprintf(ssi
, "%s\t%s\t%"TIME_PRINT_FORMAT
"\n",
401 (time_print_T
) history_item
->last_visit
) < 0)
405 if (!secure_close(ssi
)) global_history
.dirty
= 0;
409 free_global_history(void)
411 if (globhist_cache
) {
412 free_hash(&globhist_cache
);
413 globhist_cache_entries
= 0;
416 while (!list_empty(global_history
.entries
))
417 delete_global_history_item(global_history
.entries
.next
);
419 reap_deleted_globhist_items();
422 static enum evhook_status
423 global_history_write_hook(va_list ap
, void *data
)
425 write_global_history();
426 return EVENT_HOOK_STATUS_NEXT
;
429 struct event_hook_info global_history_hooks
[] = {
430 { "periodic-saving", 0, global_history_write_hook
, NULL
},
432 NULL_EVENT_HOOK_INFO
,
436 init_global_history(struct module
*module
)
438 read_global_history();
442 done_global_history(struct module
*module
)
444 write_global_history();
445 free_global_history();
446 mem_free_if(gh_last_searched_title
);
447 mem_free_if(gh_last_searched_url
);
450 struct module global_history_module
= struct_module(
451 /* name: */ N_("Global History"),
452 /* options: */ global_history_options
,
453 /* events: */ global_history_hooks
,
454 /* submodules: */ NULL
,
456 /* init: */ init_global_history
,
457 /* done: */ done_global_history