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
);
44 /* GUI stuff. Declared here because done_global_history() frees it. */
45 unsigned char *gh_last_searched_title
= NULL
;
46 unsigned char *gh_last_searched_url
= NULL
;
48 enum global_history_options
{
53 GLOBHIST_DISPLAY_TYPE
,
58 static struct option_info global_history_options
[] = {
59 INIT_OPT_TREE("document.history", N_("Global history"),
61 N_("Global history options.")),
63 INIT_OPT_BOOL("document.history.global", N_("Enable"),
65 N_("Enable global history (\"history of all pages visited\").")),
67 INIT_OPT_INT("document.history.global", N_("Maximum number of entries"),
68 "max_items", 0, 1, INT_MAX
, 1024,
69 N_("Maximum number of entries in the global history.")),
71 INIT_OPT_INT("document.history.global", N_("Display style"),
72 "display_type", 0, 0, 1, 0,
73 N_("What to display in global history dialog:\n"
77 /* Compatibility alias: added by jonas at 2004-07-16, 0.9.CVS. */
78 INIT_OPT_ALIAS("document.history.global", "write_interval", 0,
79 "infofiles.save_interval"),
84 #define get_opt_globhist(which) global_history_options[(which)].option.value
85 #define get_globhist_enable() get_opt_globhist(GLOBHIST_ENABLE).number
86 #define get_globhist_max_items() get_opt_globhist(GLOBHIST_MAX_ITEMS).number
87 #define get_globhist_display_type() get_opt_globhist(GLOBHIST_DISPLAY_TYPE).number
89 static struct hash
*globhist_cache
= NULL
;
90 static int globhist_cache_entries
= 0;
94 remove_item_from_global_history(struct global_history_item
*history_item
)
96 del_from_history_list(&global_history
, history_item
);
99 struct hash_item
*item
;
101 item
= get_hash_item(globhist_cache
, history_item
->url
, strlen(history_item
->url
));
103 del_hash_item(globhist_cache
, item
);
104 globhist_cache_entries
--;
110 done_global_history_item(struct global_history_item
*history_item
)
112 done_listbox_item(&globhist_browser
, history_item
->box_item
);
113 mem_free(history_item
->title
);
114 mem_free(history_item
->url
);
115 mem_free(history_item
);
119 delete_global_history_item(struct global_history_item
*history_item
)
121 remove_item_from_global_history(history_item
);
123 done_global_history_item(history_item
);
126 /* Search global history for item matching url. */
127 struct global_history_item
*
128 get_global_history_item(unsigned char *url
)
130 struct hash_item
*item
;
132 if (!url
|| !globhist_cache
) return NULL
;
134 /* Search for cached entry. */
136 item
= get_hash_item(globhist_cache
, url
, strlen(url
));
138 return item
? (struct global_history_item
*) item
->value
: NULL
;
142 /* Search global history for certain item. There must be full match with the
143 * parameter or the parameter must be NULL/zero. */
144 struct global_history_item
*
145 multiget_global_history_item(unsigned char *url
, unsigned char *title
, time_t time
)
147 struct global_history_item
*history_item
;
149 /* Code duplication vs performance, since this function is called most
150 * of time for url matching only... Execution time is divided by 2. */
151 if (url
&& !title
&& !time
) {
152 return get_global_history_item(url
);
154 foreach (history_item
, global_history
.items
) {
155 if ((!url
|| !strcmp(history_item
->url
, url
)) &&
156 (!title
|| !strcmp(history_item
->title
, title
)) &&
157 (!time
|| history_item
->last_visit
== time
)) {
167 static struct global_history_item
*
168 init_global_history_item(unsigned char *url
, unsigned char *title
, time_t vtime
)
170 struct global_history_item
*history_item
;
172 history_item
= mem_calloc(1, sizeof(*history_item
));
176 history_item
->last_visit
= vtime
;
177 history_item
->title
= stracpy(empty_string_or_(title
));
178 if (!history_item
->title
) {
179 mem_free(history_item
);
182 sanitize_title(history_item
->title
);
184 history_item
->url
= stracpy(url
);
185 if (!history_item
->url
|| !sanitize_url(history_item
->url
)) {
186 mem_free_if(history_item
->url
);
187 mem_free(history_item
->title
);
188 mem_free(history_item
);
192 history_item
->box_item
= add_listbox_leaf(&globhist_browser
, NULL
,
194 if (!history_item
->box_item
) {
195 mem_free(history_item
->url
);
196 mem_free(history_item
->title
);
197 mem_free(history_item
);
201 object_nolock(history_item
, "globhist");
207 cap_global_history(int max_globhist_items
)
209 while (global_history
.size
>= max_globhist_items
) {
210 struct global_history_item
*history_item
;
212 history_item
= global_history
.entries
.prev
;
214 if ((void *) history_item
== &global_history
.entries
) {
215 INTERNAL("global history is empty");
216 global_history
.size
= 0;
220 delete_global_history_item(history_item
);
227 add_item_to_global_history(struct global_history_item
*history_item
,
228 int max_globhist_items
)
230 add_to_history_list(&global_history
, history_item
);
232 /* Hash creation if needed. */
234 globhist_cache
= init_hash(8, &strhash
);
236 if (globhist_cache
&& globhist_cache_entries
< max_globhist_items
) {
237 int urllen
= strlen(history_item
->url
);
239 /* Create a new entry. */
240 if (add_hash_item(globhist_cache
, history_item
->url
, urllen
, history_item
)) {
241 globhist_cache_entries
++;
246 /* Add a new entry in history list, take care of duplicate, respect history
247 * size limit, and update any open history dialogs. */
249 add_global_history_item(unsigned char *url
, unsigned char *title
, time_t vtime
)
251 struct global_history_item
*history_item
;
252 int max_globhist_items
;
254 if (!url
|| !get_globhist_enable()) return;
256 max_globhist_items
= get_globhist_max_items();
258 history_item
= get_global_history_item(url
);
259 if (history_item
) delete_global_history_item(history_item
);
261 if (!cap_global_history(max_globhist_items
)) return;
263 history_item
= init_global_history_item(url
, title
, vtime
);
264 if (!history_item
) return;
266 add_item_to_global_history(history_item
, max_globhist_items
);
271 globhist_simple_search(unsigned char *search_url
, unsigned char *search_title
)
273 struct global_history_item
*history_item
;
275 if (!search_title
|| !search_url
)
278 /* Memorize last searched title */
279 mem_free_set(&gh_last_searched_title
, stracpy(search_title
));
280 if (!gh_last_searched_title
) return 0;
282 /* Memorize last searched url */
283 mem_free_set(&gh_last_searched_url
, stracpy(search_url
));
284 if (!gh_last_searched_url
) {
285 mem_free(gh_last_searched_title
);
289 if (!*search_title
&& !*search_url
) {
290 /* No search terms, make all entries visible. */
291 foreach (history_item
, global_history
.entries
) {
292 history_item
->box_item
->visible
= 1;
297 foreach (history_item
, global_history
.entries
) {
298 /* Make matching entries visible, hide others. */
300 && strcasestr(history_item
->title
, search_title
))
302 && strcasestr(history_item
->url
, search_url
))) {
303 history_item
->box_item
->visible
= 1;
305 history_item
->box_item
->visible
= 0;
313 read_global_history(void)
315 unsigned char in_buffer
[MAX_STR_LEN
* 3];
316 unsigned char *file_name
= GLOBAL_HISTORY_FILENAME
;
317 unsigned char *title
;
320 if (!get_globhist_enable()
321 || get_cmd_opt_bool("anonymous"))
325 file_name
= straconcat(elinks_home
, file_name
, NULL
);
326 if (!file_name
) return;
328 f
= fopen(file_name
, "rb");
329 if (elinks_home
) mem_free(file_name
);
333 global_history
.nosave
= 1;
335 while (fgets(in_buffer
, sizeof(in_buffer
), f
)) {
336 unsigned char *url
, *last_visit
, *eol
;
338 url
= strchr(title
, '\t');
340 *url
++ = '\0'; /* Now url points to the character after \t. */
342 last_visit
= strchr(url
, '\t');
343 if (!last_visit
) continue;
344 *last_visit
++ = '\0';
346 eol
= strchr(last_visit
, '\n');
348 *eol
= '\0'; /* Drop ending '\n'. */
350 add_global_history_item(url
, title
, str_to_time_t(last_visit
));
353 global_history
.nosave
= 0;
358 write_global_history(void)
360 struct global_history_item
*history_item
;
361 unsigned char *file_name
;
362 struct secure_save_info
*ssi
;
364 if (!global_history
.dirty
|| !elinks_home
365 || !get_globhist_enable()
366 || get_cmd_opt_bool("anonymous"))
369 file_name
= straconcat(elinks_home
, GLOBAL_HISTORY_FILENAME
, NULL
);
370 if (!file_name
) return;
372 ssi
= secure_open(file_name
);
376 foreachback (history_item
, global_history
.entries
) {
377 if (secure_fprintf(ssi
, "%s\t%s\t%ld\n",
380 history_item
->last_visit
) < 0) break;
383 if (!secure_close(ssi
)) global_history
.dirty
= 0;
387 free_global_history(void)
389 if (globhist_cache
) {
390 free_hash(globhist_cache
);
391 globhist_cache
= NULL
;
392 globhist_cache_entries
= 0;
395 while (!list_empty(global_history
.entries
))
396 delete_global_history_item(global_history
.entries
.next
);
399 static enum evhook_status
400 global_history_write_hook(va_list ap
, void *data
)
402 write_global_history();
403 return EVENT_HOOK_STATUS_NEXT
;
406 struct event_hook_info global_history_hooks
[] = {
407 { "periodic-saving", 0, global_history_write_hook
, NULL
},
409 NULL_EVENT_HOOK_INFO
,
413 init_global_history(struct module
*module
)
415 read_global_history();
419 done_global_history(struct module
*module
)
421 write_global_history();
422 free_global_history();
423 mem_free_if(gh_last_searched_title
);
424 mem_free_if(gh_last_searched_url
);
427 struct module global_history_module
= struct_module(
428 /* name: */ N_("Global History"),
429 /* options: */ global_history_options
,
430 /* events: */ global_history_hooks
,
431 /* submodules: */ NULL
,
433 /* init: */ init_global_history
,
434 /* done: */ done_global_history