1 /*
2  * Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <time.h>
23 
24 #include "utils/messages.h"
25 #include "utils/utils.h"
26 #include "utils/utf8.h"
27 #include "utils/libdom.h"
28 #include "utils/log.h"
29 #include "utils/nsurl.h"
30 #include "content/urldb.h"
31 
32 #include "desktop/global_history.h"
33 #include "desktop/treeview.h"
34 #include "netsurf/browser_window.h"
35 
36 #define N_DAYS 28
37 #define N_SEC_PER_DAY (60 * 60 * 24)
38 
39 enum global_history_folders {
40 	GH_TODAY = 0,
41 	GH_YESTERDAY,
42 	GH_2_DAYS_AGO,
43 	GH_3_DAYS_AGO,
44 	GH_4_DAYS_AGO,
45 	GH_5_DAYS_AGO,
46 	GH_6_DAYS_AGO,
47 	GH_LAST_WEEK,
48 	GH_2_WEEKS_AGO,
49 	GH_3_WEEKS_AGO,
50 	GH_N_FOLDERS
51 };
52 
53 enum global_history_fields {
54 	GH_TITLE,
55 	GH_URL,
56 	GH_LAST_VISIT,
57 	GH_VISITS,
58 	GH_PERIOD,
59 	N_FIELDS
60 };
61 
62 struct global_history_folder {
63 	treeview_node *folder;
64 	struct treeview_field_data data;
65 };
66 
67 struct global_history_ctx {
68 	treeview *tree;
69 	struct treeview_field_desc fields[N_FIELDS];
70 	struct global_history_folder folders[GH_N_FOLDERS];
71 	time_t today;
72 	int weekday;
73 	bool built;
74 };
75 struct global_history_ctx gh_ctx;
76 
77 struct global_history_entry {
78 	bool user_delete;
79 
80 	int slot;
81 	nsurl *url;
82 	time_t t;
83 	treeview_node *entry;
84 	struct global_history_entry *next;
85 	struct global_history_entry *prev;
86 
87 	struct treeview_field_data data[N_FIELDS - 1];
88 };
89 struct global_history_entry *gh_list[N_DAYS];
90 
91 
92 /**
93  * Find an entry in the global history
94  *
95  * \param url The URL to find
96  * \return Pointer to history entry, or NULL if not found
97  */
global_history_find(nsurl * url)98 static struct global_history_entry *global_history_find(nsurl *url)
99 {
100 	int i;
101 	struct global_history_entry *e;
102 
103 	for (i = 0; i < N_DAYS; i++) {
104 		e = gh_list[i];
105 
106 		while (e != NULL) {
107 			if (nsurl_compare(e->url, url,
108 					NSURL_COMPLETE) == true) {
109 				/* Got a match */
110 				return e;
111 			}
112 			e = e->next;
113 		}
114 
115 	}
116 
117 	/* No match found */
118 	return NULL;
119 }
120 
121 
122 /**
123  * Initialise the treeview directories
124  *
125  * \param f		Ident for folder to create
126  * \return NSERROR_OK on success, appropriate error otherwise
127  */
global_history_create_dir(enum global_history_folders f)128 static nserror global_history_create_dir(enum global_history_folders f)
129 {
130 	nserror err;
131 	treeview_node *relation = NULL;
132 	enum treeview_relationship rel = TREE_REL_FIRST_CHILD;
133 	const char *label;
134 	int i;
135 
136 	switch (f) {
137 	case GH_TODAY:
138 		label = "DateToday";
139 		break;
140 
141 	case GH_YESTERDAY:
142 		label = "DateYesterday";
143 		break;
144 
145 	case GH_2_DAYS_AGO:
146 		label = "Date2Days";
147 		break;
148 
149 	case GH_3_DAYS_AGO:
150 		label = "Date3Days";
151 		break;
152 
153 	case GH_4_DAYS_AGO:
154 		label = "Date4Days";
155 		break;
156 
157 	case GH_5_DAYS_AGO:
158 		label = "Date5Days";
159 		break;
160 
161 	case GH_6_DAYS_AGO:
162 		label = "Date6Days";
163 		break;
164 
165 	case GH_LAST_WEEK:
166 		label = "Date1Week";
167 		break;
168 
169 	case GH_2_WEEKS_AGO:
170 		label = "Date2Week";
171 		break;
172 
173 	case GH_3_WEEKS_AGO:
174 		label = "Date3Week";
175 		break;
176 
177 	default:
178 		return NSERROR_BAD_PARAMETER;
179 	}
180 
181 	label = messages_get(label);
182 
183 	for (i = f - 1; i >= 0; i--) {
184 		if (gh_ctx.folders[i].folder != NULL) {
185 			relation = gh_ctx.folders[i].folder;
186 			rel = TREE_REL_NEXT_SIBLING;
187 			break;
188 		}
189 	}
190 
191 	gh_ctx.folders[f].data.field = gh_ctx.fields[N_FIELDS - 1].field;
192 	gh_ctx.folders[f].data.value = label;
193 	gh_ctx.folders[f].data.value_len = strlen(label);
194 	err = treeview_create_node_folder(gh_ctx.tree,
195 			&gh_ctx.folders[f].folder,
196 			relation, rel,
197 			&gh_ctx.folders[f].data,
198 			&gh_ctx.folders[f],
199 			gh_ctx.built ? TREE_OPTION_NONE :
200 					TREE_OPTION_SUPPRESS_RESIZE |
201 					TREE_OPTION_SUPPRESS_REDRAW);
202 
203 	return err;
204 }
205 
206 
207 /**
208  * Get the treeview folder for history entires in a particular slot
209  *
210  * \param parent	Updated to parent folder.
211  * \param slot		Global history slot of entry we want folder node for
212  * \return NSERROR_OK on success, appropriate error otherwise
213  */
global_history_get_parent_treeview_node(treeview_node ** parent,int slot)214 static inline nserror global_history_get_parent_treeview_node(
215 		treeview_node **parent, int slot)
216 {
217 	int folder_index;
218 	struct global_history_folder *f;
219 	nserror err;
220 
221 	if (slot < 7) {
222 		folder_index = slot;
223 
224 	} else if (slot < 14) {
225 		folder_index = GH_LAST_WEEK;
226 
227 	} else if (slot < 21) {
228 		folder_index = GH_2_WEEKS_AGO;
229 
230 	} else if (slot < N_DAYS) {
231 		folder_index = GH_3_WEEKS_AGO;
232 
233 	} else {
234 		/* Slot value is invalid */
235 		return NSERROR_BAD_PARAMETER;
236 	}
237 
238 	/* Get the folder */
239 	f = &(gh_ctx.folders[folder_index]);
240 
241 	if (f->folder == NULL) {
242 		err = global_history_create_dir(folder_index);
243 		if (err != NSERROR_OK) {
244 			*parent = NULL;
245 			return err;
246 		}
247 	}
248 
249 	/* Return the parent treeview folder */
250 	*parent = f->folder;
251 	return NSERROR_OK;
252 }
253 
254 
255 /**
256  * Set a global history entry's data from the url_data.
257  *
258  * \param e		Global history entry to set up
259  * \param data	Data associated with entry's URL
260  * \return NSERROR_OK on success, appropriate error otherwise
261  */
global_history_create_treeview_field_data(struct global_history_entry * e,const struct url_data * data)262 static nserror global_history_create_treeview_field_data(
263 		struct global_history_entry *e,
264 		const struct url_data *data)
265 {
266 	const char *title = (data->title != NULL) ?
267 			data->title : messages_get("NoTitle");
268 	char buffer[16];
269 	const char *last_visited;
270 	char *last_visited2;
271 	int len;
272 
273 	e->data[GH_TITLE].field = gh_ctx.fields[GH_TITLE].field;
274 	e->data[GH_TITLE].value = strdup(title);
275 	e->data[GH_TITLE].value_len = (e->data[GH_TITLE].value != NULL) ?
276 			strlen(title) : 0;
277 
278 	e->data[GH_URL].field = gh_ctx.fields[GH_URL].field;
279 	e->data[GH_URL].value = nsurl_access(e->url);
280 	e->data[GH_URL].value_len = nsurl_length(e->url);
281 
282 	last_visited = ctime(&data->last_visit);
283 	last_visited2 = strdup(last_visited);
284 	if (last_visited2 != NULL) {
285 		assert(last_visited2[24] == '\n');
286 		last_visited2[24] = '\0';
287 	}
288 
289 	e->data[GH_LAST_VISIT].field = gh_ctx.fields[GH_LAST_VISIT].field;
290 	e->data[GH_LAST_VISIT].value = last_visited2;
291 	e->data[GH_LAST_VISIT].value_len = (last_visited2 != NULL) ? 24 : 0;
292 
293 	len = snprintf(buffer, 16, "%u", data->visits);
294 	if (len == 16) {
295 		len--;
296 		buffer[len] = '\0';
297 	}
298 
299 	e->data[GH_VISITS].field = gh_ctx.fields[GH_VISITS].field;
300 	e->data[GH_VISITS].value = strdup(buffer);
301 	e->data[GH_VISITS].value_len = len;
302 
303 	return NSERROR_OK;
304 }
305 
306 /**
307  * Add a global history entry to the treeview
308  *
309  * \param e	entry to add to treeview
310  * \param slot  global history slot containing entry
311  * \return NSERROR_OK on success, or appropriate error otherwise
312  *
313  * It is assumed that the entry is unique (for its URL) in the global
314  * history table
315  */
global_history_entry_insert(struct global_history_entry * e,int slot)316 static nserror global_history_entry_insert(struct global_history_entry *e,
317 		int slot)
318 {
319 	nserror err;
320 
321 	treeview_node *parent;
322 	err = global_history_get_parent_treeview_node(&parent, slot);
323 	if (err != NSERROR_OK) {
324 		return err;
325 	}
326 
327 	err = treeview_create_node_entry(gh_ctx.tree, &(e->entry),
328 			parent, TREE_REL_FIRST_CHILD, e->data, e,
329 			gh_ctx.built ? TREE_OPTION_NONE :
330 					TREE_OPTION_SUPPRESS_RESIZE |
331 					TREE_OPTION_SUPPRESS_REDRAW);
332 	if (err != NSERROR_OK) {
333 		return err;
334 	}
335 
336 	return NSERROR_OK;
337 }
338 
339 
340 /**
341  * Add an entry to the global history (creates the entry).
342  *
343  * If the treeview has already been created, the entry will be added to the
344  * treeview.  Otherwise, the entry will have to be added to the treeview later.
345  *
346  * When we first create the global history we create it without the treeview, to
347  * simplfy sorting the entries.
348  *
349  * \param url		URL for entry to add to history
350  * \param slot		Global history slot to contain history entry
351  * \param data		URL data for the entry
352  * \param got_treeview	Whether the treeview has been created already
353  * \return NSERROR_OK on success, or appropriate error otherwise
354  */
global_history_add_entry_internal(nsurl * url,int slot,const struct url_data * data,bool got_treeview)355 static nserror global_history_add_entry_internal(nsurl *url, int slot,
356 		const struct url_data *data, bool got_treeview)
357 {
358 	nserror err;
359 	struct global_history_entry *e;
360 
361 	/* Create new local history entry */
362 	e = malloc(sizeof(struct global_history_entry));
363 	if (e == NULL) {
364 		return NSERROR_NOMEM;
365 	}
366 
367 	e->user_delete = false;
368 	e->slot = slot;
369 	e->url = nsurl_ref(url);
370 	e->t = data->last_visit;
371 	e->entry = NULL;
372 	e->next = NULL;
373 	e->prev = NULL;
374 
375 	err = global_history_create_treeview_field_data(e, data);
376 	if (err != NSERROR_OK) {
377 		return err;
378 	}
379 
380 	if (gh_list[slot] == NULL) {
381 		/* list empty */
382 		gh_list[slot] = e;
383 
384 	} else if (gh_list[slot]->t < e->t) {
385 		/* Insert at list head */
386 		e->next = gh_list[slot];
387 		gh_list[slot]->prev = e;
388 		gh_list[slot] = e;
389 	} else {
390 		struct global_history_entry *prev = gh_list[slot];
391 		struct global_history_entry *curr = prev->next;
392 		while (curr != NULL) {
393 			if (curr->t < e->t) {
394 				break;
395 			}
396 			prev = curr;
397 			curr = curr->next;
398 		}
399 
400 		/* insert after prev */
401 		e->next = curr;
402 		e->prev = prev;
403 		prev->next = e;
404 
405 		if (curr != NULL)
406 			curr->prev = e;
407 	}
408 
409 	if (got_treeview) {
410 		err = global_history_entry_insert(e, slot);
411 		if (err != NSERROR_OK) {
412 			return err;
413 		}
414 	}
415 
416 	return NSERROR_OK;
417 }
418 
419 
420 /**
421  * Delete a global history entry
422  *
423  * This does not delete the treeview node, rather it should only be called from
424  * the treeview node delete event message.
425  *
426  * \param e		Entry to delete
427  */
global_history_delete_entry_internal(struct global_history_entry * e)428 static void global_history_delete_entry_internal(
429 		struct global_history_entry *e)
430 {
431 	assert(e != NULL);
432 	assert(e->entry == NULL);
433 
434 	/* Unlink */
435 	if (gh_list[e->slot] == e) {
436 		/* e is first entry */
437 		gh_list[e->slot] = e->next;
438 
439 		if (e->next != NULL)
440 			e->next->prev = NULL;
441 
442 	} else if (e->next == NULL) {
443 		/* e is last entry */
444 		e->prev->next = NULL;
445 
446 	} else {
447 		/* e has an entry before and after */
448 		e->prev->next = e->next;
449 		e->next->prev = e->prev;
450 	}
451 
452 	if (e->user_delete) {
453 		/* User requested delete, so delete from urldb too. */
454 		urldb_reset_url_visit_data(e->url);
455 	}
456 
457 	/* Destroy fields */
458 	free((void *)e->data[GH_TITLE].value); /* Eww */
459 	free((void *)e->data[GH_LAST_VISIT].value); /* Eww */
460 	free((void *)e->data[GH_VISITS].value); /* Eww */
461 	nsurl_unref(e->url);
462 
463 	/* Destroy entry */
464 	free(e);
465 }
466 
467 /**
468  * Internal routine to actually perform global history addition
469  *
470  * \param url The URL to add
471  * \param data URL data associated with URL
472  * \return true (for urldb_iterate_entries)
473  */
global_history_add_entry(nsurl * url,const struct url_data * data)474 static bool global_history_add_entry(nsurl *url,
475 		const struct url_data *data)
476 {
477 	int slot;
478 	time_t visit_date;
479 	time_t earliest_date = gh_ctx.today - (N_DAYS - 1) * N_SEC_PER_DAY;
480 	bool got_treeview = gh_ctx.tree != NULL;
481 
482 	assert((url != NULL) && (data != NULL));
483 
484 	visit_date = data->last_visit;
485 
486 	/* Find day array slot for entry */
487 	if (visit_date >= gh_ctx.today) {
488 		slot = 0;
489 	} else if (visit_date >= earliest_date) {
490 		slot = (gh_ctx.today - visit_date) / N_SEC_PER_DAY + 1;
491 	} else {
492 		/* too old */
493 		return true;
494 	}
495 
496 	if (got_treeview == true) {
497 		/* The treeview for global history already exists */
498 		struct global_history_entry *e;
499 
500 		/* Delete any existing entry for this URL */
501 		e = global_history_find(url);
502 		if (e != NULL) {
503 			treeview_delete_node(gh_ctx.tree, e->entry,
504 					TREE_OPTION_SUPPRESS_REDRAW |
505 					TREE_OPTION_SUPPRESS_RESIZE);
506 		}
507 	}
508 
509 	if (global_history_add_entry_internal(url, slot, data,
510 			got_treeview) != NSERROR_OK) {
511 		return false;
512 	}
513 
514 	return true;
515 }
516 
517 /**
518  * Initialise the treeview entry feilds
519  *
520  * \return NSERROR_OK on success, or appropriate error otherwise
521  */
global_history_initialise_entry_fields(void)522 static nserror global_history_initialise_entry_fields(void)
523 {
524 	int i;
525 	const char *label;
526 
527 	for (i = 0; i < N_FIELDS; i++)
528 		gh_ctx.fields[i].field = NULL;
529 
530 	gh_ctx.fields[GH_TITLE].flags = TREE_FLAG_DEFAULT;
531 	label = "TreeviewLabelTitle";
532 	label = messages_get(label);
533 	if (lwc_intern_string(label, strlen(label),
534 			&gh_ctx.fields[GH_TITLE].field) !=
535 			lwc_error_ok) {
536 		goto error;
537 	}
538 
539 	gh_ctx.fields[GH_URL].flags =
540 			TREE_FLAG_COPY_TEXT |
541 			TREE_FLAG_SEARCHABLE;
542 	label = "TreeviewLabelURL";
543 	label = messages_get(label);
544 	if (lwc_intern_string(label, strlen(label),
545 			&gh_ctx.fields[GH_URL].field) !=
546 			lwc_error_ok) {
547 		goto error;
548 	}
549 
550 	gh_ctx.fields[GH_LAST_VISIT].flags = TREE_FLAG_SHOW_NAME;
551 	label = "TreeviewLabelLastVisit";
552 	label = messages_get(label);
553 	if (lwc_intern_string(label, strlen(label),
554 			&gh_ctx.fields[GH_LAST_VISIT].field) !=
555 			lwc_error_ok) {
556 		goto error;
557 	}
558 
559 	gh_ctx.fields[GH_VISITS].flags = TREE_FLAG_SHOW_NAME;
560 	label = "TreeviewLabelVisits";
561 	label = messages_get(label);
562 	if (lwc_intern_string(label, strlen(label),
563 			&gh_ctx.fields[GH_VISITS].field) !=
564 			lwc_error_ok) {
565 		goto error;
566 	}
567 
568 	gh_ctx.fields[GH_PERIOD].flags = TREE_FLAG_DEFAULT;
569 	label = "TreeviewLabelPeriod";
570 	label = messages_get(label);
571 	if (lwc_intern_string(label, strlen(label),
572 			&gh_ctx.fields[GH_PERIOD].field) !=
573 			lwc_error_ok) {
574 		return false;
575 	}
576 
577 	return NSERROR_OK;
578 
579 error:
580 	for (i = 0; i < N_FIELDS; i++)
581 		if (gh_ctx.fields[i].field != NULL)
582 			lwc_string_unref(gh_ctx.fields[i].field);
583 
584 	return NSERROR_UNKNOWN;
585 }
586 
587 
588 /**
589  * Initialise the time
590  *
591  * \return NSERROR_OK on success, or appropriate error otherwise
592  */
global_history_initialise_time(void)593 static nserror global_history_initialise_time(void)
594 {
595 	struct tm *full_time;
596 	time_t t;
597 
598 	/* get the current time */
599 	t = time(NULL);
600 	if (t == -1) {
601 		NSLOG(netsurf, INFO, "time info unaviable");
602 		return NSERROR_UNKNOWN;
603 	}
604 
605 	/* get the time at the start of today */
606 	full_time = localtime(&t);
607 	full_time->tm_sec = 0;
608 	full_time->tm_min = 0;
609 	full_time->tm_hour = 0;
610 	t = mktime(full_time);
611 	if (t == -1) {
612 		NSLOG(netsurf, INFO, "mktime failed");
613 		return NSERROR_UNKNOWN;
614 	}
615 
616 	gh_ctx.today = t;
617 	gh_ctx.weekday = full_time->tm_wday;
618 
619 	return NSERROR_OK;
620 }
621 
622 
623 /**
624  * Initialise the treeview entries
625  *
626  * \return NSERROR_OK on success, or appropriate error otherwise
627  */
global_history_init_entries(void)628 static nserror global_history_init_entries(void)
629 {
630 	int i;
631 	nserror err;
632 
633 	/* Itterate over all global history data, inserting it into treeview */
634 	for (i = 0; i < N_DAYS; i++) {
635 		struct global_history_entry *l = NULL;
636 		struct global_history_entry *e = gh_list[i];
637 
638 		/* Insert in reverse order; find last */
639 		while (e != NULL) {
640 			l = e;
641 			e = e->next;
642 		}
643 
644 		/* Insert the entries into the treeview */
645 		while (l != NULL) {
646 			err = global_history_entry_insert(l, i);
647 			if (err != NSERROR_OK) {
648 				return err;
649 			}
650 			l = l->prev;
651 		}
652 	}
653 
654 	return NSERROR_OK;
655 }
656 
657 
global_history_tree_node_folder_cb(struct treeview_node_msg msg,void * data)658 static nserror global_history_tree_node_folder_cb(
659 		struct treeview_node_msg msg, void *data)
660 {
661 	struct global_history_folder *f = data;
662 
663 	switch (msg.msg) {
664 	case TREE_MSG_NODE_DELETE:
665 		f->folder = NULL;
666 		break;
667 
668 	case TREE_MSG_NODE_EDIT:
669 		break;
670 
671 	case TREE_MSG_NODE_LAUNCH:
672 		break;
673 	}
674 
675 	return NSERROR_OK;
676 }
677 
678 static nserror
global_history_tree_node_entry_cb(struct treeview_node_msg msg,void * data)679 global_history_tree_node_entry_cb(struct treeview_node_msg msg, void *data)
680 {
681 	struct global_history_entry *e = data;
682 	nserror ret = NSERROR_OK;
683 
684 	switch (msg.msg) {
685 	case TREE_MSG_NODE_DELETE:
686 		e->entry = NULL;
687 		e->user_delete = msg.data.delete.user;
688 		global_history_delete_entry_internal(e);
689 		break;
690 
691 	case TREE_MSG_NODE_EDIT:
692 		break;
693 
694 	case TREE_MSG_NODE_LAUNCH:
695 	{
696 		struct browser_window *existing = NULL;
697 		enum browser_window_create_flags flags = BW_CREATE_HISTORY;
698 
699 		/* TODO: Set existing to window that new tab appears in */
700 
701 		if (msg.data.node_launch.mouse &
702 				(BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2) ||
703 				existing == NULL) {
704 			/* Shift or Ctrl launch, open in new window rather
705 			 * than tab. */
706 			/* TODO: flags ^= BW_CREATE_TAB; */
707 		}
708 
709 		ret = browser_window_create(flags, e->url, NULL,
710 				existing, NULL);
711 	}
712 		break;
713 	}
714 	return ret;
715 }
716 
717 struct treeview_callback_table gh_tree_cb_t = {
718 	.folder = global_history_tree_node_folder_cb,
719 	.entry = global_history_tree_node_entry_cb
720 };
721 
722 
723 /* Exported interface, documented in global_history.h */
global_history_init(struct core_window_callback_table * cw_t,void * core_window_handle)724 nserror global_history_init(struct core_window_callback_table *cw_t,
725 		void *core_window_handle)
726 {
727 	nserror err;
728 
729 	err = treeview_init();
730 	if (err != NSERROR_OK) {
731 		return err;
732 	}
733 
734 	NSLOG(netsurf, INFO, "Loading global history");
735 
736 	/* Init. global history treeview time */
737 	err = global_history_initialise_time();
738 	if (err != NSERROR_OK) {
739 		gh_ctx.tree = NULL;
740 		return err;
741 	}
742 
743 	/* Init. global history treeview entry fields */
744 	err = global_history_initialise_entry_fields();
745 	if (err != NSERROR_OK) {
746 		gh_ctx.tree = NULL;
747 		return err;
748 	}
749 
750 	/* Load the entries */
751 	urldb_iterate_entries(global_history_add_entry);
752 
753 	/* Create the global history treeview */
754 	err = treeview_create(&gh_ctx.tree, &gh_tree_cb_t,
755 			N_FIELDS, gh_ctx.fields,
756 			cw_t, core_window_handle,
757 			TREEVIEW_NO_MOVES | TREEVIEW_DEL_EMPTY_DIRS |
758 			TREEVIEW_SEARCHABLE);
759 	if (err != NSERROR_OK) {
760 		gh_ctx.tree = NULL;
761 		return err;
762 	}
763 
764 	/* Ensure there is a folder for today */
765 	err = global_history_create_dir(GH_TODAY);
766 	if (err != NSERROR_OK) {
767 		return err;
768 	}
769 
770 	/* Add the history to the treeview */
771 	err = global_history_init_entries();
772 	if (err != NSERROR_OK) {
773 		return err;
774 	}
775 
776 	/* Expand the "Today" folder node */
777 	err = treeview_node_expand(gh_ctx.tree,
778 			gh_ctx.folders[GH_TODAY].folder);
779 	if (err != NSERROR_OK) {
780 		return err;
781 	}
782 
783 	/* History tree is built
784 	 * We suppress the treeview height callback on entry insertion before
785 	 * the treeview is built. */
786 	gh_ctx.built = true;
787 
788 	/* Inform client of window height */
789 	treeview_get_height(gh_ctx.tree);
790 
791 	NSLOG(netsurf, INFO, "Loaded global history");
792 
793 	return NSERROR_OK;
794 }
795 
796 
797 /* Exported interface, documented in global_history.h */
global_history_fini(void)798 nserror global_history_fini(void)
799 {
800 	int i;
801 	nserror err;
802 
803 	NSLOG(netsurf, INFO, "Finalising global history");
804 
805 	gh_ctx.built = false;
806 
807 	/* Destroy the global history treeview */
808 	err = treeview_destroy(gh_ctx.tree);
809 	gh_ctx.tree = NULL;
810 
811 	/* Free global history treeview entry fields */
812 	for (i = 0; i < N_FIELDS; i++)
813 		if (gh_ctx.fields[i].field != NULL)
814 			lwc_string_unref(gh_ctx.fields[i].field);
815 
816 	err = treeview_fini();
817 	if (err != NSERROR_OK) {
818 		return err;
819 	}
820 
821 	NSLOG(netsurf, INFO, "Finalised global history");
822 
823 	return err;
824 }
825 
826 
827 /* Exported interface, documented in global_history.h */
global_history_add(nsurl * url)828 nserror global_history_add(nsurl *url)
829 {
830 	const struct url_data *data;
831 
832 	/* If we don't have a global history at the moment, just return OK */
833 	if (gh_ctx.tree == NULL)
834 		return NSERROR_OK;
835 
836 	data = urldb_get_url_data(url);
837 	if (data == NULL) {
838 		NSLOG(netsurf, INFO,
839 		      "Can't add URL to history that's not present in urldb.");
840 		return NSERROR_BAD_PARAMETER;
841 	}
842 
843 	global_history_add_entry(url, data);
844 
845 	return NSERROR_OK;
846 }
847 
848 
849 struct treeview_export_walk_ctx {
850 	FILE *fp;
851 };
852 /** Callback for treeview_walk node entering */
global_history_export_enter_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)853 static nserror global_history_export_enter_cb(void *ctx, void *node_data,
854 		enum treeview_node_type type, bool *abort)
855 {
856 	struct treeview_export_walk_ctx *tw = ctx;
857 	nserror ret;
858 
859 	if (type == TREE_NODE_ENTRY) {
860 		struct global_history_entry *e = node_data;
861 		char *t_text;
862 		char *u_text;
863 
864 		ret = utf8_to_html(e->data[GH_TITLE].value, "iso-8859-1",
865 				e->data[GH_TITLE].value_len, &t_text);
866 		if (ret != NSERROR_OK)
867 			return NSERROR_SAVE_FAILED;
868 
869 		ret = utf8_to_html(e->data[GH_URL].value, "iso-8859-1",
870 				e->data[GH_URL].value_len, &u_text);
871 		if (ret != NSERROR_OK) {
872 			free(t_text);
873 			return NSERROR_SAVE_FAILED;
874 		}
875 
876 		fprintf(tw->fp, "<li><a href=\"%s\">%s</a></li>\n",
877 			u_text, t_text);
878 
879 		free(t_text);
880 		free(u_text);
881 
882 	} else if (type == TREE_NODE_FOLDER) {
883 		struct global_history_folder *f = node_data;
884 		char *f_text;
885 
886 		ret = utf8_to_html(f->data.value, "iso-8859-1",
887 				f->data.value_len, &f_text);
888 		if (ret != NSERROR_OK)
889 			return NSERROR_SAVE_FAILED;
890 
891 		fprintf(tw->fp, "<li><h4>%s</h4>\n<ul>\n", f_text);
892 
893 		free(f_text);
894 	}
895 
896 	return NSERROR_OK;
897 }
898 /** Callback for treeview_walk node leaving */
global_history_export_leave_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)899 static nserror global_history_export_leave_cb(void *ctx, void *node_data,
900 		enum treeview_node_type type, bool *abort)
901 {
902 	struct treeview_export_walk_ctx *tw = ctx;
903 
904 	if (type == TREE_NODE_FOLDER) {
905 		fputs("</ul></li>\n", tw->fp);
906 	}
907 
908 	return NSERROR_OK;
909 }
910 /* Exported interface, documented in global_history.h */
global_history_export(const char * path,const char * title)911 nserror global_history_export(const char *path, const char *title)
912 {
913 	struct treeview_export_walk_ctx tw;
914 	nserror err;
915 	FILE *fp;
916 
917 	fp = fopen(path, "w");
918 	if (fp == NULL)
919 		return NSERROR_SAVE_FAILED;
920 
921 	if (title == NULL)
922 		title = "NetSurf Browsing History";
923 
924 	fputs("<!DOCTYPE html "
925 		"PUBLIC \"//W3C/DTD HTML 4.01//EN\" "
926 		"\"http://www.w3.org/TR/html4/strict.dtd\">\n", fp);
927 	fputs("<html>\n<head>\n", fp);
928 	fputs("<meta http-equiv=\"Content-Type\" "
929 		"content=\"text/html; charset=iso-8859-1\">\n", fp);
930 	fprintf(fp, "<title>%s</title>\n", title);
931 	fputs("</head>\n<body>\n<ul>\n", fp);
932 
933 	tw.fp = fp;
934 	err = treeview_walk(gh_ctx.tree, NULL,
935 			global_history_export_enter_cb,
936 			global_history_export_leave_cb,
937 			&tw, TREE_NODE_ENTRY | TREE_NODE_FOLDER);
938 	if (err != NSERROR_OK)
939 		return err;
940 
941 	fputs("</ul>\n</body>\n</html>\n", fp);
942 
943 	fclose(fp);
944 
945 	return NSERROR_OK;
946 }
947 
948 
949 /* Exported interface, documented in global_history.h */
global_history_redraw(int x,int y,struct rect * clip,const struct redraw_context * ctx)950 void global_history_redraw(int x, int y, struct rect *clip,
951 		const struct redraw_context *ctx)
952 {
953 	treeview_redraw(gh_ctx.tree, x, y, clip, ctx);
954 }
955 
956 
957 /* Exported interface, documented in global_history.h */
global_history_mouse_action(browser_mouse_state mouse,int x,int y)958 void global_history_mouse_action(browser_mouse_state mouse, int x, int y)
959 {
960 	treeview_mouse_action(gh_ctx.tree, mouse, x, y);
961 }
962 
963 
964 /* Exported interface, documented in global_history.h */
global_history_keypress(uint32_t key)965 bool global_history_keypress(uint32_t key)
966 {
967 	return treeview_keypress(gh_ctx.tree, key);
968 }
969 
970 
971 /* Exported interface, documented in global_history.h */
global_history_has_selection(void)972 bool global_history_has_selection(void)
973 {
974 	return treeview_has_selection(gh_ctx.tree);
975 }
976 
977 
978 /* Exported interface, documented in global_history.h */
global_history_get_selection(nsurl ** url,const char ** title)979 bool global_history_get_selection(nsurl **url, const char **title)
980 {
981 	struct global_history_entry *e;
982 	enum treeview_node_type type;
983 	void *v;
984 
985 	type = treeview_get_selection(gh_ctx.tree, &v);
986 	if (type != TREE_NODE_ENTRY || v == NULL) {
987 		*url = NULL;
988 		*title = NULL;
989 		return false;
990 	}
991 
992 	e = (struct global_history_entry *)v;
993 
994 	*url = e->url;
995 	*title = e->data[GH_TITLE].value;
996 	return true;
997 }
998 
999 
1000 /* Exported interface, documented in global_history.h */
global_history_expand(bool only_folders)1001 nserror global_history_expand(bool only_folders)
1002 {
1003 	return treeview_expand(gh_ctx.tree, only_folders);
1004 }
1005 
1006 
1007 /* Exported interface, documented in global_history.h */
global_history_contract(bool all)1008 nserror global_history_contract(bool all)
1009 {
1010 	return treeview_contract(gh_ctx.tree, all);
1011 }
1012 
1013