1 /*
2  * Copyright 2012 John-Mark Bell <jmb@netsurf-browser.org>
3  * Copyright 2013 Michael Drake <tlsa@netsurf-browser.org>
4  *
5  * This file is part of NetSurf, http://www.netsurf-browser.org/
6  *
7  * NetSurf is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; version 2 of the License.
10  *
11  * NetSurf is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <errno.h>
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include <dom/dom.h>
26 #include <dom/bindings/hubbub/parser.h>
27 
28 #include "utils/corestrings.h"
29 #include "utils/messages.h"
30 #include "utils/utils.h"
31 #include "utils/utf8.h"
32 #include "utils/libdom.h"
33 #include "utils/log.h"
34 #include "utils/nsurl.h"
35 #include "content/urldb.h"
36 
37 #include "netsurf/misc.h"
38 #include "desktop/gui_internal.h"
39 #include "desktop/hotlist.h"
40 #include "desktop/treeview.h"
41 #include "netsurf/browser_window.h"
42 
43 #define N_DAYS 28
44 #define N_SEC_PER_DAY (60 * 60 * 24)
45 
46 enum hotlist_fields {
47 	HL_TITLE,
48 	HL_URL,
49 	HL_LAST_VISIT,
50 	HL_VISITS,
51 	HL_FOLDER,
52 	HL_N_FIELDS
53 };
54 
55 struct hotlist_folder {
56 	treeview_node *folder;
57 	struct treeview_field_data data;
58 };
59 
60 struct hotlist_ctx {
61 	treeview *tree;
62 	struct treeview_field_desc fields[HL_N_FIELDS];
63 	bool built;
64 	struct hotlist_folder *default_folder;
65 	char *save_path;
66 	bool save_scheduled;
67 };
68 struct hotlist_ctx hl_ctx;
69 
70 struct hotlist_entry {
71 	nsurl *url;
72 	treeview_node *entry;
73 
74 	struct treeview_field_data data[HL_N_FIELDS - 1];
75 };
76 
77 
78 /*
79  * Get path for writing hotlist to
80  *
81  * \param path		The final path of the hotlist
82  * \param loaded	Updated to the path to write the holist to
83  * \return NSERROR_OK on success, or appropriate error otherwise
84  */
hotlist_get_temp_path(const char * path,char ** temp_path)85 static nserror hotlist_get_temp_path(const char *path, char **temp_path)
86 {
87 	const char *extension = "-bk";
88 	char *joined;
89 	int len;
90 
91 	len = strlen(path) + strlen(extension);
92 
93 	joined = malloc(len + 1);
94 	if (joined == NULL) {
95 		return NSERROR_NOMEM;
96 	}
97 
98 	if (snprintf(joined, len + 1, "%s%s", path, extension) != len) {
99 		free(joined);
100 		return NSERROR_UNKNOWN;
101 	}
102 
103 	*temp_path = joined;
104 	return NSERROR_OK;
105 }
106 
107 
108 /* Save the hotlist to to a file at the given path
109  *
110  * \param path  Path to save hotlist file to.  NULL path is a no-op.
111  * \return NSERROR_OK on success, or appropriate error otherwise
112  */
hotlist_save(const char * path)113 static nserror hotlist_save(const char *path)
114 {
115 	nserror res = NSERROR_OK;
116 	char *temp_path;
117 
118 	/* NULL path is a no-op. */
119 	if (path == NULL) {
120 		return NSERROR_OK;
121 	}
122 
123 	/* Get path to export to */
124 	res = hotlist_get_temp_path(path, &temp_path);
125 	if (res != NSERROR_OK) {
126 		return res;
127 	}
128 
129 	/* Export to temp path */
130 	res = hotlist_export(temp_path, NULL);
131 	if (res != NSERROR_OK) {
132 		goto cleanup;
133 	}
134 
135 	/* Remove old hotlist to handle non-POSIX rename() implementations. */
136 	(void)remove(path);
137 
138 	/* Replace any old hotlist file with the one we just saved */
139 	if (rename(temp_path, path) != 0) {
140 		res = NSERROR_SAVE_FAILED;
141 		NSLOG(netsurf, INFO, "Error renaming hotlist: %s.",
142 		      strerror(errno));
143 		goto cleanup;
144 	}
145 
146 cleanup:
147 	free(temp_path);
148 
149 	return res;
150 }
151 
152 
153 /**
154  * Scheduler callback for saving the hotlist.
155  *
156  * \param p  Unused user data.
157  */
hotlist_schedule_save_cb(void * p)158 static void hotlist_schedule_save_cb(void *p)
159 {
160 	hl_ctx.save_scheduled = false;
161 	hotlist_save(hl_ctx.save_path);
162 }
163 
164 
165 /**
166  * Schedule a hotlist save.
167  *
168  * \return NSERROR_OK on success, or appropriate error otherwise
169  */
hotlist_schedule_save(void)170 static nserror hotlist_schedule_save(void)
171 {
172 	if (hl_ctx.save_scheduled == false && hl_ctx.save_path != NULL) {
173 		nserror err = guit->misc->schedule(10 * 1000,
174 				hotlist_schedule_save_cb, NULL);
175 		if (err != NSERROR_OK) {
176 			return err;
177 		}
178 		hl_ctx.save_scheduled = true;
179 	}
180 
181 	return NSERROR_OK;
182 }
183 
184 
185 /**
186  * Set a hotlist entry's data from the url_data.
187  *
188  * \param e    hotlist entry to set up.
189  * \param data Data associated with entry's URL.
190  * \return NSERROR_OK on success, appropriate error otherwise.
191  */
hotlist_create_treeview_field_visits_data(struct hotlist_entry * e,const struct url_data * data)192 static nserror hotlist_create_treeview_field_visits_data(
193 		struct hotlist_entry *e, const struct url_data *data)
194 {
195 	char buffer[16];
196 	const char *last_visited;
197 	char *last_visited2;
198 	int len;
199 
200 	/* Last visited */
201 	if (data->visits != 0) {
202 		last_visited = ctime(&data->last_visit);
203 		last_visited2 = strdup(last_visited);
204 		len = 24;
205 	} else {
206 		last_visited2 = strdup("-");
207 		len = 1;
208 	}
209 	if (last_visited2 == NULL) {
210 		return NSERROR_NOMEM;
211 
212 	} else if (len == 24) {
213 		assert(last_visited2[24] == '\n');
214 		last_visited2[24] = '\0';
215 	}
216 
217 	e->data[HL_LAST_VISIT].field = hl_ctx.fields[HL_LAST_VISIT].field;
218 	e->data[HL_LAST_VISIT].value = last_visited2;
219 	e->data[HL_LAST_VISIT].value_len = len;
220 
221 	/* Visits */
222 	len = snprintf(buffer, 16, "%u", data->visits);
223 	if (len == 16) {
224 		len--;
225 		buffer[len] = '\0';
226 	}
227 
228 	e->data[HL_VISITS].field = hl_ctx.fields[HL_VISITS].field;
229 	e->data[HL_VISITS].value = strdup(buffer);
230 	if (e->data[HL_VISITS].value == NULL) {
231 		free((void*)e->data[HL_LAST_VISIT].value);
232 		return NSERROR_NOMEM;
233 	}
234 	e->data[HL_VISITS].value_len = len;
235 
236 	return NSERROR_OK;
237 }
238 
239 
240 /**
241  * Set a hotlist entry's data from the url_data.
242  *
243  * \param e     hotlist entry to set up
244  * \param title Title for entry, or NULL if using title from data
245  * \param data  Data associated with entry's URL
246  * \return NSERROR_OK on success, appropriate error otherwise
247  */
hotlist_create_treeview_field_data(struct hotlist_entry * e,const char * title,const struct url_data * data)248 static nserror hotlist_create_treeview_field_data(
249 		struct hotlist_entry *e, const char *title,
250 		const struct url_data *data)
251 {
252 	nserror err;
253 
254 	/* "URL" field */
255 	e->data[HL_URL].field = hl_ctx.fields[HL_URL].field;
256 	e->data[HL_URL].value = nsurl_access(e->url);
257 	e->data[HL_URL].value_len = nsurl_length(e->url);
258 
259 	/* "Title" field */
260 	if (title == NULL) {
261 		/* Title not provided; use one from URL data */
262 		title = (data->title != NULL) ?
263 				data->title : nsurl_access(e->url);
264 	}
265 
266 	e->data[HL_TITLE].field = hl_ctx.fields[HL_TITLE].field;
267 	e->data[HL_TITLE].value = strdup(title);
268 	if (e->data[HL_TITLE].value == NULL) {
269 		return NSERROR_NOMEM;
270 	}
271 	e->data[HL_TITLE].value_len = (e->data[HL_TITLE].value != NULL) ?
272 			strlen(title) : 0;
273 
274 	/* "Last visited" and "Visits" fields */
275 	err = hotlist_create_treeview_field_visits_data(e, data);
276 	if (err != NSERROR_OK) {
277 		free((void*)e->data[HL_TITLE].value);
278 		return NSERROR_OK;
279 	}
280 
281 	return NSERROR_OK;
282 }
283 
284 
285 /**
286  * Add a hotlist entry to the treeview
287  *
288  * \param e		Entry to add to treeview
289  * \param relation	Existing node to insert as relation of, or NULL
290  * \param rel		Folder's relationship to relation
291  * \return NSERROR_OK on success, or appropriate error otherwise
292  *
293  * It is assumed that the entry is unique (for its URL) in the global
294  * hotlist table
295  */
hotlist_entry_insert(struct hotlist_entry * e,treeview_node * relation,enum treeview_relationship rel)296 static nserror hotlist_entry_insert(struct hotlist_entry *e,
297 		treeview_node *relation, enum treeview_relationship rel)
298 {
299 	nserror err;
300 
301 	err = treeview_create_node_entry(hl_ctx.tree, &(e->entry),
302 			relation, rel, e->data, e, hl_ctx.built ?
303 			TREE_OPTION_NONE : TREE_OPTION_SUPPRESS_RESIZE |
304 					TREE_OPTION_SUPPRESS_REDRAW);
305 	if (err != NSERROR_OK) {
306 		return err;
307 	}
308 
309 	return NSERROR_OK;
310 }
311 
312 
313 /**
314  * Delete a hotlist entry
315  *
316  * This does not delete the treeview node, rather it should only be called from
317  * the treeview node delete event message.
318  *
319  * \param e		Entry to delete
320  */
hotlist_delete_entry_internal(struct hotlist_entry * e)321 static void hotlist_delete_entry_internal(struct hotlist_entry *e)
322 {
323 	assert(e != NULL);
324 	assert(e->entry == NULL);
325 
326 	/* Destroy fields */
327 	free((void *)e->data[HL_TITLE].value); /* Eww */
328 	free((void *)e->data[HL_LAST_VISIT].value); /* Eww */
329 	free((void *)e->data[HL_VISITS].value); /* Eww */
330 	nsurl_unref(e->url);
331 
332 	/* Destroy entry */
333 	free(e);
334 }
335 
336 
337 /**
338  * Create hotlist entry data for URL.
339  *
340  * \param url		URL for entry to add to hotlist.
341  * \param title		Title for entry, or NULL if using title from data
342  * \param data		URL data for the entry, or NULL
343  * \param entry		Updated to new hotlist entry data
344  * \return NSERROR_OK on success, or appropriate error otherwise
345  */
hotlist_create_entry(nsurl * url,const char * title,const struct url_data * data,struct hotlist_entry ** entry)346 static nserror hotlist_create_entry(nsurl *url, const char *title,
347 		const struct url_data *data, struct hotlist_entry **entry)
348 {
349 	nserror err;
350 	struct hotlist_entry *e;
351 
352 	assert(url != NULL);
353 
354 	*entry = NULL;
355 
356 	if (data == NULL) {
357 		/* Get the URL data */
358 		data = urldb_get_url_data(url);
359 		if (data == NULL) {
360 			/* No entry in database, so add one */
361 			urldb_add_url(url);
362 			/* now attempt to get url data */
363 			data = urldb_get_url_data(url);
364 		}
365 		if (data == NULL) {
366 			return NSERROR_NOMEM;
367 		}
368 	}
369 
370 	/* Create new local hotlist entry */
371 	e = malloc(sizeof(struct hotlist_entry));
372 	if (e == NULL) {
373 		return NSERROR_NOMEM;
374 	}
375 
376 	e->url = nsurl_ref(url);
377 	e->entry = NULL;
378 
379 	err = hotlist_create_treeview_field_data(e, title, data);
380 	if (err != NSERROR_OK) {
381 		nsurl_unref(e->url);
382 		free(e);
383 		return err;
384 	}
385 
386 	*entry = e;
387 
388 	return NSERROR_OK;
389 }
390 
391 
392 /**
393  * Add an entry to the hotlist (creates the entry).
394  *
395  * \param url		URL for entry to add to hotlist.
396  * \param title		Title for entry, or NULL if using title from data
397  * \param data		URL data for the entry, or NULL
398  * \param relation	Existing node to insert as relation of, or NULL
399  * \param rel		Entry's relationship to relation
400  * \param entry		Updated to new treeview entry node
401  * \return NSERROR_OK on success, or appropriate error otherwise
402  */
hotlist_add_entry_internal(nsurl * url,const char * title,const struct url_data * data,treeview_node * relation,enum treeview_relationship rel,treeview_node ** entry)403 static nserror hotlist_add_entry_internal(nsurl *url, const char *title,
404 		const struct url_data *data, treeview_node *relation,
405 		enum treeview_relationship rel, treeview_node **entry)
406 {
407 	nserror err;
408 	struct hotlist_entry *e;
409 
410 	err = hotlist_create_entry(url, title, data, &e);
411 	if (err != NSERROR_OK) {
412 		return err;
413 	}
414 
415 	err = hotlist_entry_insert(e, relation, rel);
416 	if (err != NSERROR_OK) {
417 		hotlist_delete_entry_internal(e);
418 		return err;
419 	}
420 
421 	/* Make this URL persistent */
422 	urldb_set_url_persistence(url, true);
423 
424 	*entry = e->entry;
425 
426 	return NSERROR_OK;
427 }
428 
429 
430 /**
431  * Add folder to the hotlist (creates the folder).
432  *
433  * \param title		Title for folder, or NULL if using title from data
434  * \param relation	Existing node to insert as relation of, or NULL
435  * \param rel		Folder's relationship to relation
436  * \param folder	Updated to new hotlist folder data
437  * \param default_folder Add to the default folder.
438  * \return NSERROR_OK on success, or appropriate error otherwise
439  */
hotlist_add_folder_internal(const char * title,treeview_node * relation,enum treeview_relationship rel,struct hotlist_folder ** folder,bool default_folder)440 static nserror hotlist_add_folder_internal(
441 		const char *title, treeview_node *relation,
442 		enum treeview_relationship rel, struct hotlist_folder **folder,
443 		bool default_folder)
444 {
445 	struct hotlist_folder *f;
446 	treeview_node_options_flags flags = TREE_OPTION_NONE;
447 	treeview_node *n;
448 	nserror err;
449 
450 	if (title == NULL) {
451 		title = messages_get("NewFolderName");
452 	}
453 
454 	/* Create the title field */
455 	f = malloc(sizeof(struct hotlist_folder));
456 	if (f == NULL) {
457 		return NSERROR_NOMEM;
458 	}
459 	f->data.field = hl_ctx.fields[HL_FOLDER].field;
460 	f->data.value = strdup(title);
461 	if (f->data.value == NULL) {
462 		free(f);
463 		return NSERROR_NOMEM;
464 	}
465 	f->data.value_len = strlen(title);
466 
467 	if (!hl_ctx.built)
468 		flags |= TREE_OPTION_SUPPRESS_RESIZE |
469 				TREE_OPTION_SUPPRESS_REDRAW;
470 	if (default_folder)
471 		flags |= TREE_OPTION_SPECIAL_DIR;
472 
473 	err = treeview_create_node_folder(hl_ctx.tree,
474 			&n, relation, rel, &f->data, f, flags);
475 	if (err != NSERROR_OK) {
476 		free((void*)f->data.value); /* Eww */
477 		free(f);
478 		return err;
479 	}
480 
481 	f->folder = n;
482 	*folder = f;
483 
484 	return NSERROR_OK;
485 }
486 
487 
hotlist_tree_node_folder_cb(struct treeview_node_msg msg,void * data)488 static nserror hotlist_tree_node_folder_cb(
489 		struct treeview_node_msg msg, void *data)
490 {
491 	struct hotlist_folder *f = data;
492 	const char *old_text;
493 	bool match;
494 
495 	switch (msg.msg) {
496 	case TREE_MSG_NODE_DELETE:
497 		if (f == hl_ctx.default_folder)
498 			hl_ctx.default_folder = NULL;
499 		free((void*)f->data.value); /* Eww */
500 		free(f);
501 		break;
502 
503 	case TREE_MSG_NODE_EDIT:
504 		if (lwc_string_isequal(hl_ctx.fields[HL_FOLDER].field,
505 				msg.data.node_edit.field, &match) ==
506 				lwc_error_ok && match == true &&
507 				msg.data.node_edit.text != NULL &&
508 				msg.data.node_edit.text[0] != '\0') {
509 			/* Requst to change the folder title text */
510 			old_text = f->data.value;
511 			f->data.value = strdup(msg.data.node_edit.text);
512 
513 			if (f->data.value == NULL) {
514 				f->data.value = old_text;
515 			} else {
516 				f->data.value_len = strlen(f->data.value);
517 				treeview_update_node_folder(hl_ctx.tree,
518 						f->folder, &f->data, f);
519 				free((void *)old_text);
520 			}
521 		}
522 		break;
523 
524 	case TREE_MSG_NODE_LAUNCH:
525 		break;
526 	}
527 
528 	return NSERROR_OK;
529 }
530 
531 /**
532  * callback for hotlist treeview entry manipulation.
533  */
534 static nserror
hotlist_tree_node_entry_cb(struct treeview_node_msg msg,void * data)535 hotlist_tree_node_entry_cb(struct treeview_node_msg msg, void *data)
536 {
537 	struct hotlist_entry *e = data;
538 	const char *old_text;
539 	nsurl *old_url;
540 	nsurl *url;
541 	nserror err = NSERROR_OK;
542 	bool match;
543 
544 	switch (msg.msg) {
545 	case TREE_MSG_NODE_DELETE:
546 		e->entry = NULL;
547 		hotlist_delete_entry_internal(e);
548 
549 		err = hotlist_schedule_save();
550 		break;
551 
552 	case TREE_MSG_NODE_EDIT:
553 		if (lwc_string_isequal(hl_ctx.fields[HL_TITLE].field,
554 				msg.data.node_edit.field, &match) ==
555 				lwc_error_ok && match == true &&
556 				msg.data.node_edit.text != NULL &&
557 				msg.data.node_edit.text[0] != '\0') {
558 			/* Requst to change the entry title text */
559 			old_text = e->data[HL_TITLE].value;
560 			e->data[HL_TITLE].value =
561 					strdup(msg.data.node_edit.text);
562 
563 			if (e->data[HL_TITLE].value == NULL) {
564 				e->data[HL_TITLE].value = old_text;
565 			} else {
566 				e->data[HL_TITLE].value_len =
567 						strlen(e->data[HL_TITLE].value);
568 				treeview_update_node_entry(hl_ctx.tree,
569 						e->entry, e->data, e);
570 				free((void *)old_text);
571 			}
572 
573 			err = hotlist_schedule_save();
574 
575 		} else if (lwc_string_isequal(hl_ctx.fields[HL_URL].field,
576 				msg.data.node_edit.field, &match) ==
577 				lwc_error_ok && match == true &&
578 				msg.data.node_edit.text != NULL &&
579 				msg.data.node_edit.text[0] != '\0') {
580 			/* Requst to change the entry URL text */
581 			err = nsurl_create(msg.data.node_edit.text, &url);
582 			if (err == NSERROR_OK) {
583 				old_url = e->url;
584 
585 				e->url = url;
586 				e->data[HL_URL].value = nsurl_access(url);
587 				e->data[HL_URL].value_len = nsurl_length(e->url);
588 
589 				treeview_update_node_entry(hl_ctx.tree,
590 						   e->entry, e->data, e);
591 				nsurl_unref(old_url);
592 
593 				err = hotlist_schedule_save();
594 			}
595 		}
596 		break;
597 
598 	case TREE_MSG_NODE_LAUNCH:
599 	{
600 		struct browser_window *existing = NULL;
601 		enum browser_window_create_flags flags = BW_CREATE_HISTORY;
602 
603 		/* TODO: Set existing to window that new tab appears in */
604 
605 		if (msg.data.node_launch.mouse &
606 				(BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2) ||
607 				existing == NULL) {
608 			/* Shift or Ctrl launch, open in new window rather
609 			 * than tab. */
610 			/* TODO: flags ^= BW_CREATE_TAB; */
611 		}
612 
613 		err = browser_window_create(flags, e->url, NULL,
614 				existing, NULL);
615 	}
616 		break;
617 	}
618 	return err;
619 }
620 
621 struct treeview_callback_table hl_tree_cb_t = {
622 	.folder = hotlist_tree_node_folder_cb,
623 	.entry = hotlist_tree_node_entry_cb
624 };
625 
626 
627 
628 typedef struct {
629 	treeview *tree;
630 	treeview_node *rel;
631 	enum treeview_relationship relshp;
632 	bool last_was_h4;
633 	dom_string *title;
634 } hotlist_load_ctx;
635 
636 
637 /**
638  * Parse an entry represented as a li.
639  *
640  * \param li DOM node for parsed li
641  * \param ctx Our hotlist loading context.
642  * \return NSERROR_OK on success, or appropriate error otherwise
643  */
hotlist_load_entry(dom_node * li,hotlist_load_ctx * ctx)644 static nserror hotlist_load_entry(dom_node *li, hotlist_load_ctx *ctx)
645 {
646 	dom_node *a;
647 	dom_string *title1, *url1;
648 	const char *title;
649 	nsurl *url;
650 	dom_exception derror;
651 	nserror err;
652 
653 	/* The li must contain an "a" element */
654 	a = libdom_find_first_element(li, corestring_lwc_a);
655 	if (a == NULL) {
656 		NSLOG(netsurf, INFO, "Missing <a> in <li>");
657 		return NSERROR_INVALID;
658 	}
659 
660 	derror = dom_node_get_text_content(a, &title1);
661 	if (derror != DOM_NO_ERR) {
662 		NSLOG(netsurf, INFO, "No title");
663 		dom_node_unref(a);
664 		return NSERROR_INVALID;
665 	}
666 
667 	derror = dom_element_get_attribute(a, corestring_dom_href, &url1);
668 	if (derror != DOM_NO_ERR || url1 == NULL) {
669 		NSLOG(netsurf, INFO, "No URL");
670 		dom_string_unref(title1);
671 		dom_node_unref(a);
672 		return NSERROR_INVALID;
673 	}
674 	dom_node_unref(a);
675 
676 	if (title1 != NULL) {
677 		title = dom_string_data(title1);
678 	} else {
679 		title = messages_get("NoTitle");
680 	}
681 
682 	/* Need to get URL as a nsurl object */
683 	err = nsurl_create(dom_string_data(url1), &url);
684 	dom_string_unref(url1);
685 
686 	if (err != NSERROR_OK) {
687 		NSLOG(netsurf, INFO, "Failed normalising '%s'",
688 		      dom_string_data(url1));
689 
690 		if (title1 != NULL) {
691 			dom_string_unref(title1);
692 		}
693 
694 		return err;
695 	}
696 
697 	/* Add the entry */
698 	err = hotlist_add_entry_internal(url, title, NULL, ctx->rel,
699 			ctx->relshp, &ctx->rel);
700 	nsurl_unref(url);
701 	if (title1 != NULL) {
702 		dom_string_unref(title1);
703 	}
704 	ctx->relshp = TREE_REL_NEXT_SIBLING;
705 
706 	if (err != NSERROR_OK) {
707 		return err;
708 	}
709 
710 	return NSERROR_OK;
711 }
712 
713 
714 /*
715  * Callback for libdom_iterate_child_elements, which despite the namespace is
716  * a NetSurf function.
717  *
718  * \param node		Node that is a child of the directory UL node
719  * \param ctx		Our hotlist loading context.
720  */
721 static nserror hotlist_load_directory_cb(dom_node *node, void *ctx);
722 
723 /**
724  * Parse a directory represented as a ul.
725  *
726  * \param ul DOM node for parsed ul.
727  * \param ctx The hotlist context.
728  * \return NSERROR_OK on success, or appropriate error otherwise
729  */
hotlist_load_directory(dom_node * ul,hotlist_load_ctx * ctx)730 static nserror hotlist_load_directory(dom_node *ul, hotlist_load_ctx *ctx)
731 {
732 	assert(ul != NULL);
733 	assert(ctx != NULL);
734 
735 	return libdom_iterate_child_elements(ul,
736 			hotlist_load_directory_cb, ctx);
737 }
738 
739 
740 /* Documented above, in forward declaration */
hotlist_load_directory_cb(dom_node * node,void * ctx)741 nserror hotlist_load_directory_cb(dom_node *node, void *ctx)
742 {
743 	/* TODO: return appropriate errors */
744 	hotlist_load_ctx *current_ctx = ctx;
745 	dom_string *name;
746 	dom_exception error;
747 	nserror err;
748 
749 	/* The ul may contain entries as a li, or directories as
750 	 * an h4 followed by a ul. Non-element nodes may be present
751 	 * (eg. text, comments), and are ignored. */
752 
753 	error = dom_node_get_node_name(node, &name);
754 	if (error != DOM_NO_ERR || name == NULL)
755 		return NSERROR_DOM;
756 
757 	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_li)) {
758 		/* Entry handling */
759 		hotlist_load_entry(node, current_ctx);
760 		current_ctx->last_was_h4 = false;
761 
762 	} else if (dom_string_caseless_lwc_isequal(name, corestring_lwc_h4)) {
763 		/* Directory handling, part 1: Get title from H4 */
764 		dom_string *title;
765 
766 		error = dom_node_get_text_content(node, &title);
767 		if (error != DOM_NO_ERR || title == NULL) {
768 			NSLOG(netsurf, INFO,
769 			      "Empty <h4> or memory exhausted.");
770 			dom_string_unref(name);
771 			return NSERROR_DOM;
772 		}
773 
774 		if (current_ctx->title != NULL)
775 			dom_string_unref(current_ctx->title);
776 		current_ctx->title = title;
777 		current_ctx->last_was_h4 = true;
778 
779 	} else if (current_ctx->last_was_h4 &&
780 			dom_string_caseless_lwc_isequal(name,
781 					corestring_lwc_ul)) {
782 		/* Directory handling, part 2: Make node, and handle children */
783 		const char *title;
784 		dom_string *id;
785 		struct hotlist_folder *f;
786 		hotlist_load_ctx new_ctx;
787 		treeview_node *rel;
788 		bool default_folder = false;
789 
790 		/* Check if folder should be default folder */
791 		error = dom_element_get_attribute(node, corestring_dom_id, &id);
792 		if (error != DOM_NO_ERR) {
793 			dom_string_unref(name);
794 			return NSERROR_NOMEM;
795 		}
796 		if (id != NULL) {
797 			if (dom_string_lwc_isequal(id, corestring_lwc_default))
798 				default_folder = true;
799 
800 			dom_string_unref(id);
801 		}
802 
803 		title = dom_string_data(current_ctx->title);
804 
805 		/* Add folder node */
806 		err = hotlist_add_folder_internal(title, current_ctx->rel,
807 				current_ctx->relshp, &f, default_folder);
808 		if (err != NSERROR_OK) {
809 			dom_string_unref(name);
810 			return NSERROR_NOMEM;
811 		}
812 
813 		if (default_folder)
814 			hl_ctx.default_folder = f;
815 
816 		rel = f->folder;
817 		current_ctx->rel = rel;
818 		current_ctx->relshp = TREE_REL_NEXT_SIBLING;
819 
820 		new_ctx.tree = current_ctx->tree;
821 		new_ctx.rel = rel;
822 		new_ctx.relshp = TREE_REL_FIRST_CHILD;
823 		new_ctx.last_was_h4 = false;
824 		new_ctx.title = NULL;
825 
826 		/* And load its contents */
827 		err = hotlist_load_directory(node, &new_ctx);
828 		if (err != NSERROR_OK) {
829 			dom_string_unref(name);
830 			return NSERROR_NOMEM;
831 		}
832 
833 		if (new_ctx.title != NULL) {
834 			dom_string_unref(new_ctx.title);
835 			new_ctx.title = NULL;
836 		}
837 		current_ctx->last_was_h4 = false;
838 	} else {
839 		current_ctx->last_was_h4 = false;
840 	}
841 
842 	dom_string_unref(name);
843 
844 	return NSERROR_OK;
845 }
846 
847 
848 /*
849  * Load the hotlist data from file
850  *
851  * \param path		The path to load the hotlist file from, or NULL
852  * \param loaded	Updated to true iff hotlist file loaded, else set false
853  * \return NSERROR_OK on success, or appropriate error otherwise
854  */
hotlist_load(const char * path,bool * loaded)855 static nserror hotlist_load(const char *path, bool *loaded)
856 {
857 	dom_document *document;
858 	dom_node *html, *body, *ul;
859 	hotlist_load_ctx ctx;
860 	char *temp_path;
861 	nserror err;
862 
863 	*loaded = false;
864 
865 	/* Handle no path */
866 	if (path == NULL) {
867 		NSLOG(netsurf, INFO, "No hotlist file path provided.");
868 		return NSERROR_OK;
869 	}
870 
871 	/* Get temp hotlist write path */
872 	err = hotlist_get_temp_path(path, &temp_path);
873 	if (err != NSERROR_OK) {
874 		return err;
875 	}
876 
877 	/* Load hotlist file */
878 	err = libdom_parse_file(path, "iso-8859-1", &document);
879 	if (err != NSERROR_OK) {
880 		err = libdom_parse_file(temp_path, "iso-8859-1", &document);
881 		if (err != NSERROR_OK) {
882 			free(temp_path);
883 			return err;
884 		}
885 	}
886 	free(temp_path);
887 
888 	/* Find HTML element */
889 	html = libdom_find_first_element((dom_node *) document,
890 			corestring_lwc_html);
891 	if (html == NULL) {
892 		dom_node_unref(document);
893 		NSLOG(netsurf, WARNING,
894 		      "%s (<html> not found)",
895 		      messages_get("TreeLoadError"));
896 		return NSERROR_OK;
897 	}
898 
899 	/* Find BODY element */
900 	body = libdom_find_first_element(html, corestring_lwc_body);
901 	if (body == NULL) {
902 		dom_node_unref(html);
903 		dom_node_unref(document);
904 		NSLOG(netsurf, WARNING,
905 		      "%s (<html>...<body> not found)",
906 		      messages_get("TreeLoadError"));
907 		return NSERROR_OK;
908 	}
909 
910 	/* Find UL element */
911 	ul = libdom_find_first_element(body, corestring_lwc_ul);
912 	if (ul == NULL) {
913 		dom_node_unref(body);
914 		dom_node_unref(html);
915 		dom_node_unref(document);
916 		NSLOG(netsurf, WARNING,
917 		      "%s (<html>...<body>...<ul> not found.)",
918 		      messages_get("TreeLoadError"));
919 		return NSERROR_OK;
920 	}
921 
922 	/* Set up the hotlist loading context */
923 	ctx.tree = hl_ctx.tree;
924 	ctx.rel = NULL;
925 	ctx.relshp = TREE_REL_FIRST_CHILD;
926 	ctx.last_was_h4 = false;
927 	ctx.title = NULL;
928 
929 	err = hotlist_load_directory(ul, &ctx);
930 
931 	if (ctx.title != NULL) {
932 		dom_string_unref(ctx.title);
933 		ctx.title = NULL;
934 	}
935 
936 	dom_node_unref(ul);
937 	dom_node_unref(body);
938 	dom_node_unref(html);
939 	dom_node_unref(document);
940 
941 	if (err != NSERROR_OK) {
942 		NSLOG(netsurf, WARNING,
943 		      "%s (Failed building tree.)",
944 		      messages_get("TreeLoadError"));
945 		return NSERROR_OK;
946 	}
947 
948 	*loaded = true;
949 
950 	return NSERROR_OK;
951 }
952 
953 
954 /*
955  * Generate default hotlist
956  *
957  * \return NSERROR_OK on success, or appropriate error otherwise
958  */
hotlist_generate(void)959 static nserror hotlist_generate(void)
960 {
961 	int i;
962 	struct hotlist_folder *f;
963 	treeview_node *e;
964 	const char *title;
965 	nserror err;
966 	nsurl *url;
967 	static const struct {
968 		const char *url;
969 		const char *msg_key;
970 	} default_entries[] = {
971 		{ "http://www.netsurf-browser.org/",
972 				"HotlistHomepage" },
973 		{ "http://www.netsurf-browser.org/downloads/",
974 				"HotlistDownloads" },
975 		{ "http://www.netsurf-browser.org/documentation",
976 				"HotlistDocumentation" },
977 		{ "http://www.netsurf-browser.org/contact",
978 				"HotlistContact" }
979 	};
980 	const int n_entries = sizeof(default_entries) /
981 			sizeof(default_entries[0]);
982 
983 	/* First make "NetSurf" folder for defualt entries */
984 	title = "NetSurf";
985 	err = hotlist_add_folder_internal(title, NULL,
986 			TREE_REL_FIRST_CHILD, &f, false);
987 	if (err != NSERROR_OK) {
988 		return err;
989 	}
990 
991 	/* And add entries as children of folder node */
992 	for (i = n_entries - 1; i >= 0; i--) {
993 		/* Get URL as nsurl object */
994 		err = nsurl_create(default_entries[i].url, &url);
995 		if (err != NSERROR_OK) {
996 			return NSERROR_NOMEM;
997 		}
998 
999 		title = messages_get(default_entries[i].msg_key);
1000 
1001 		/* Build the node */
1002 		err = hotlist_add_entry_internal(url, title,
1003 				NULL, f->folder, TREE_REL_FIRST_CHILD, &e);
1004 		nsurl_unref(url);
1005 
1006 		if (err != NSERROR_OK) {
1007 			return NSERROR_NOMEM;
1008 		}
1009 	}
1010 
1011 	return NSERROR_OK;
1012 }
1013 
1014 
1015 struct treeview_export_walk_ctx {
1016 	FILE *fp;
1017 };
1018 
1019 /** Callback for treeview_walk node entering */
hotlist_export_enter_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1020 static nserror hotlist_export_enter_cb(void *ctx, void *node_data,
1021 		enum treeview_node_type type, bool *abort)
1022 {
1023 	struct treeview_export_walk_ctx *tw = ctx;
1024 	nserror ret;
1025 
1026 	if (type == TREE_NODE_ENTRY) {
1027 		struct hotlist_entry *e = node_data;
1028 		char *t_text;
1029 		char *u_text;
1030 
1031 		ret = utf8_to_html(e->data[HL_TITLE].value, "iso-8859-1",
1032 				e->data[HL_TITLE].value_len, &t_text);
1033 		if (ret != NSERROR_OK)
1034 			return NSERROR_SAVE_FAILED;
1035 
1036 		ret = utf8_to_html(e->data[HL_URL].value, "iso-8859-1",
1037 				e->data[HL_URL].value_len, &u_text);
1038 		if (ret != NSERROR_OK) {
1039 			free(t_text);
1040 			return NSERROR_SAVE_FAILED;
1041 		}
1042 
1043 		fprintf(tw->fp, "<li><a href=\"%s\">%s</a></li>\n",
1044 			u_text, t_text);
1045 
1046 		free(t_text);
1047 		free(u_text);
1048 
1049 	} else if (type == TREE_NODE_FOLDER) {
1050 		struct hotlist_folder *f = node_data;
1051 		char *f_text;
1052 
1053 		ret = utf8_to_html(f->data.value, "iso-8859-1",
1054 				f->data.value_len, &f_text);
1055 		if (ret != NSERROR_OK)
1056 			return NSERROR_SAVE_FAILED;
1057 
1058 		if (f == hl_ctx.default_folder) {
1059 			fprintf(tw->fp, "<h4>%s</h4>\n<ul id=\"default\">\n",
1060 					f_text);
1061 		} else {
1062 			fprintf(tw->fp, "<h4>%s</h4>\n<ul>\n", f_text);
1063 		}
1064 
1065 		free(f_text);
1066 	}
1067 
1068 	return NSERROR_OK;
1069 }
1070 /** Callback for treeview_walk node leaving */
hotlist_export_leave_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1071 static nserror hotlist_export_leave_cb(void *ctx, void *node_data,
1072 		enum treeview_node_type type, bool *abort)
1073 {
1074 	struct treeview_export_walk_ctx *tw = ctx;
1075 
1076 	if (type == TREE_NODE_FOLDER) {
1077 		fputs("</ul>\n", tw->fp);
1078 	}
1079 
1080 	return NSERROR_OK;
1081 }
1082 /* Exported interface, documented in hotlist.h */
hotlist_export(const char * path,const char * title)1083 nserror hotlist_export(const char *path, const char *title)
1084 {
1085 	struct treeview_export_walk_ctx tw;
1086 	nserror err;
1087 	FILE *fp;
1088 
1089 	fp = fopen(path, "w");
1090 	if (fp == NULL)
1091 		return NSERROR_SAVE_FAILED;
1092 
1093 	if (title == NULL)
1094 		title = "NetSurf hotlist";
1095 
1096 	/* The Acorn Browse Hotlist format, which we mimic[*], is invalid HTML
1097 	 * claming to be valid.
1098 	 * [*] Why? */
1099 	fputs("<!DOCTYPE html "
1100 		"PUBLIC \"//W3C/DTD HTML 4.01//EN\" "
1101 		"\"http://www.w3.org/TR/html4/strict.dtd\">\n", fp);
1102 	fputs("<html>\n<head>\n", fp);
1103 	fputs("<meta http-equiv=\"Content-Type\" "
1104 		"content=\"text/html; charset=iso-8859-1\">\n", fp);
1105 	fprintf(fp, "<title>%s</title>\n", title);
1106 	fputs("</head>\n<body>\n<ul>\n", fp);
1107 
1108 	tw.fp = fp;
1109 	err = treeview_walk(hl_ctx.tree, NULL,
1110 			hotlist_export_enter_cb,
1111 			hotlist_export_leave_cb,
1112 			&tw, TREE_NODE_ENTRY | TREE_NODE_FOLDER);
1113 	if (err != NSERROR_OK)
1114 		return err;
1115 
1116 	fputs("</ul>\n</body>\n</html>\n", fp);
1117 
1118 	fclose(fp);
1119 
1120 	return NSERROR_OK;
1121 }
1122 
1123 
1124 struct hotlist_iterate_ctx {
1125 	hotlist_folder_enter_cb enter_cb;
1126 	hotlist_address_cb address_cb;
1127 	hotlist_folder_leave_cb leave_cb;
1128 	void *ctx;
1129 };
1130 /** Callback for hotlist_iterate node entering */
hotlist_iterate_enter_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1131 static nserror hotlist_iterate_enter_cb(void *ctx, void *node_data,
1132 		enum treeview_node_type type, bool *abort)
1133 {
1134 	struct hotlist_iterate_ctx *data = ctx;
1135 
1136 	if (type == TREE_NODE_ENTRY && data->address_cb != NULL) {
1137 		struct hotlist_entry *e = node_data;
1138 		data->address_cb(data->ctx, e->url,
1139 				e->data[HL_TITLE].value);
1140 
1141 	} else if (type == TREE_NODE_FOLDER && data->enter_cb != NULL) {
1142 		struct hotlist_folder *f = node_data;
1143 		data->enter_cb(data->ctx, f->data.value);
1144 	}
1145 
1146 	return NSERROR_OK;
1147 }
1148 /** Callback for hotlist_iterate node leaving */
hotlist_iterate_leave_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1149 static nserror hotlist_iterate_leave_cb(void *ctx, void *node_data,
1150 		enum treeview_node_type type, bool *abort)
1151 {
1152 	struct hotlist_iterate_ctx *data = ctx;
1153 
1154 	if (type == TREE_NODE_FOLDER && data->leave_cb != NULL) {
1155 		data->leave_cb(data->ctx);
1156 	}
1157 
1158 	return NSERROR_OK;
1159 }
1160 /* Exported interface, documented in hotlist.h */
hotlist_iterate(void * ctx,hotlist_folder_enter_cb enter_cb,hotlist_address_cb address_cb,hotlist_folder_leave_cb leave_cb)1161 nserror hotlist_iterate(void *ctx,
1162 		hotlist_folder_enter_cb enter_cb,
1163 		hotlist_address_cb address_cb,
1164 		hotlist_folder_leave_cb leave_cb)
1165 {
1166 	struct hotlist_iterate_ctx data;
1167 	nserror err;
1168 
1169 	data.enter_cb = enter_cb;
1170 	data.address_cb = address_cb;
1171 	data.leave_cb = leave_cb;
1172 	data.ctx = ctx;
1173 
1174 	err = treeview_walk(hl_ctx.tree, NULL,
1175 			hotlist_iterate_enter_cb,
1176 			hotlist_iterate_leave_cb,
1177 			&data, TREE_NODE_ENTRY | TREE_NODE_FOLDER);
1178 	if (err != NSERROR_OK)
1179 		return err;
1180 
1181 	return NSERROR_OK;
1182 }
1183 
1184 
1185 /**
1186  * Initialise the treeview entry feilds
1187  *
1188  * \return NSERROR_OK on success, or appropriate error otherwise
1189  */
hotlist_initialise_entry_fields(void)1190 static nserror hotlist_initialise_entry_fields(void)
1191 {
1192 	int i;
1193 	const char *label;
1194 
1195 	for (i = 0; i < HL_N_FIELDS; i++)
1196 		hl_ctx.fields[i].field = NULL;
1197 
1198 	hl_ctx.fields[HL_TITLE].flags = TREE_FLAG_DEFAULT |
1199 			TREE_FLAG_ALLOW_EDIT;
1200 	label = "TreeviewLabelTitle";
1201 	label = messages_get(label);
1202 	if (lwc_intern_string(label, strlen(label),
1203 			&hl_ctx.fields[HL_TITLE].field) !=
1204 			lwc_error_ok) {
1205 		goto error;
1206 	}
1207 
1208 	hl_ctx.fields[HL_URL].flags =
1209 			TREE_FLAG_ALLOW_EDIT |
1210 			TREE_FLAG_COPY_TEXT |
1211 			TREE_FLAG_SEARCHABLE;
1212 	label = "TreeviewLabelURL";
1213 	label = messages_get(label);
1214 	if (lwc_intern_string(label, strlen(label),
1215 			&hl_ctx.fields[HL_URL].field) !=
1216 			lwc_error_ok) {
1217 		goto error;
1218 	}
1219 
1220 	hl_ctx.fields[HL_LAST_VISIT].flags = TREE_FLAG_SHOW_NAME;
1221 	label = "TreeviewLabelLastVisit";
1222 	label = messages_get(label);
1223 	if (lwc_intern_string(label, strlen(label),
1224 			&hl_ctx.fields[HL_LAST_VISIT].field) !=
1225 			lwc_error_ok) {
1226 		goto error;
1227 	}
1228 
1229 	hl_ctx.fields[HL_VISITS].flags = TREE_FLAG_SHOW_NAME;
1230 	label = "TreeviewLabelVisits";
1231 	label = messages_get(label);
1232 	if (lwc_intern_string(label, strlen(label),
1233 			&hl_ctx.fields[HL_VISITS].field) !=
1234 			lwc_error_ok) {
1235 		goto error;
1236 	}
1237 
1238 	hl_ctx.fields[HL_FOLDER].flags = TREE_FLAG_DEFAULT |
1239 			TREE_FLAG_ALLOW_EDIT;
1240 	label = "TreeviewLabelFolder";
1241 	label = messages_get(label);
1242 	if (lwc_intern_string(label, strlen(label),
1243 			&hl_ctx.fields[HL_FOLDER].field) !=
1244 			lwc_error_ok) {
1245 		return false;
1246 	}
1247 
1248 	return NSERROR_OK;
1249 
1250 error:
1251 	for (i = 0; i < HL_N_FIELDS; i++)
1252 		if (hl_ctx.fields[i].field != NULL)
1253 			lwc_string_unref(hl_ctx.fields[i].field);
1254 
1255 	return NSERROR_UNKNOWN;
1256 }
1257 
1258 
1259 /*
1260  * Populate the hotlist from file, or generate default hotlist if no file
1261  *
1262  * \param path		The path to load the hotlist file from, or NULL
1263  * \return NSERROR_OK on success, or appropriate error otherwise
1264  */
hotlist_populate(const char * path)1265 static nserror hotlist_populate(const char *path)
1266 {
1267 	nserror err;
1268 	bool loaded;
1269 
1270 	/* Load hotlist file */
1271 	err = hotlist_load(path, &loaded);
1272 
1273 	if (loaded && err == NSERROR_OK)
1274 		return err;
1275 
1276 	/* Couldn't load hotlist, generate a default one */
1277 	err = hotlist_generate();
1278 	if (err != NSERROR_OK) {
1279 		return err;
1280 	}
1281 
1282 	return NSERROR_OK;
1283 }
1284 
1285 
1286 /* Exported interface, documented in hotlist.h */
hotlist_init(const char * load_path,const char * save_path)1287 nserror hotlist_init(
1288 		const char *load_path,
1289 		const char *save_path)
1290 {
1291 	nserror err;
1292 
1293 	err = treeview_init();
1294 	if (err != NSERROR_OK) {
1295 		return err;
1296 	}
1297 
1298 	NSLOG(netsurf, INFO, "Loading hotlist");
1299 
1300 	hl_ctx.tree = NULL;
1301 	hl_ctx.built = false;
1302 	hl_ctx.default_folder = NULL;
1303 
1304 	/* Store the save path */
1305 	if (save_path != NULL) {
1306 		hl_ctx.save_path = strdup(save_path);
1307 		if (hl_ctx.save_path == NULL) {
1308 			return NSERROR_NOMEM;
1309 		}
1310 	} else {
1311 		hl_ctx.save_path = NULL;
1312 	}
1313 
1314 	/* Init. hotlist treeview entry fields */
1315 	err = hotlist_initialise_entry_fields();
1316 	if (err != NSERROR_OK) {
1317 		free(hl_ctx.save_path);
1318 		hl_ctx.tree = NULL;
1319 		return err;
1320 	}
1321 
1322 	/* Create the hotlist treeview */
1323 	err = treeview_create(&hl_ctx.tree, &hl_tree_cb_t,
1324 			HL_N_FIELDS, hl_ctx.fields, NULL, NULL,
1325 			TREEVIEW_SEARCHABLE);
1326 	if (err != NSERROR_OK) {
1327 		free(hl_ctx.save_path);
1328 		hl_ctx.tree = NULL;
1329 		return err;
1330 	}
1331 
1332 	/* Populate the hotlist */
1333 	err = hotlist_populate(load_path);
1334 	if (err != NSERROR_OK) {
1335 		free(hl_ctx.save_path);
1336 		return err;
1337 	}
1338 
1339 	/* Hotlist tree is built
1340 	 * We suppress the treeview height callback on entry insertion before
1341 	 * the treeview is built. */
1342 	hl_ctx.built = true;
1343 
1344 	NSLOG(netsurf, INFO, "Loaded hotlist");
1345 
1346 	return NSERROR_OK;
1347 }
1348 
1349 
1350 /* Exported interface, documented in hotlist.h */
hotlist_manager_init(struct core_window_callback_table * cw_t,void * core_window_handle)1351 nserror hotlist_manager_init(struct core_window_callback_table *cw_t,
1352 		void *core_window_handle)
1353 {
1354 	nserror err;
1355 
1356 	/* Create the hotlist treeview */
1357 	err = treeview_cw_attach(hl_ctx.tree, cw_t, core_window_handle);
1358 	if (err != NSERROR_OK) {
1359 		return err;
1360 	}
1361 
1362 	/* Inform client of window height */
1363 	treeview_get_height(hl_ctx.tree);
1364 
1365 	return NSERROR_OK;
1366 }
1367 
1368 
1369 /* Exported interface, documented in hotlist.h */
hotlist_manager_fini(void)1370 nserror hotlist_manager_fini(void)
1371 {
1372 	nserror err;
1373 
1374 	/* Create the hotlist treeview */
1375 	err = treeview_cw_detach(hl_ctx.tree);
1376 	if (err != NSERROR_OK) {
1377 		return err;
1378 	}
1379 
1380 	return NSERROR_OK;
1381 }
1382 
1383 
1384 /* Exported interface, documented in hotlist.h */
hotlist_fini(void)1385 nserror hotlist_fini(void)
1386 {
1387 	int i;
1388 	nserror err;
1389 
1390 	NSLOG(netsurf, INFO, "Finalising hotlist");
1391 
1392 	/* Remove any existing scheduled save callback */
1393 	guit->misc->schedule(-1, hotlist_schedule_save_cb, NULL);
1394 	hl_ctx.save_scheduled = false;
1395 
1396 	/* Save the hotlist */
1397 	err = hotlist_save(hl_ctx.save_path);
1398 	if (err != NSERROR_OK) {
1399 		NSLOG(netsurf, INFO, "Problem saving the hotlist.");
1400 	}
1401 
1402 	free(hl_ctx.save_path);
1403 
1404 	/* Destroy the hotlist treeview */
1405 	err = treeview_destroy(hl_ctx.tree);
1406 	if (err != NSERROR_OK) {
1407 		NSLOG(netsurf, INFO, "Problem destroying the hotlist treeview.");
1408 	}
1409 	hl_ctx.built = false;
1410 
1411 	/* Free hotlist treeview entry fields */
1412 	for (i = 0; i < HL_N_FIELDS; i++)
1413 		if (hl_ctx.fields[i].field != NULL)
1414 			lwc_string_unref(hl_ctx.fields[i].field);
1415 
1416 	err = treeview_fini();
1417 	if (err != NSERROR_OK) {
1418 		return err;
1419 	}
1420 
1421 	NSLOG(netsurf, INFO, "Finalised hotlist");
1422 
1423 	return err;
1424 }
1425 
1426 
1427 /* Exported interface, documented in hotlist.h */
hotlist_add_url(nsurl * url)1428 nserror hotlist_add_url(nsurl *url)
1429 {
1430 	treeview_node *entry;
1431 	nserror err;
1432 
1433 	/* If we don't have a hotlist at the moment, just return OK */
1434 	if (hl_ctx.tree == NULL)
1435 		return NSERROR_OK;
1436 
1437 	/* Make the default folder, if we don't have one */
1438 	if (hl_ctx.default_folder == NULL) {
1439 		const char *temp = messages_get("HotlistDefaultFolderName");
1440 		struct hotlist_folder *f;
1441 		err = hotlist_add_folder_internal(temp, NULL,
1442 				TREE_REL_FIRST_CHILD, &f, true);
1443 		if (err != NSERROR_OK)
1444 			return err;
1445 
1446 		if (f == NULL)
1447 			return NSERROR_NOMEM;
1448 
1449 		hl_ctx.default_folder = f;
1450 	}
1451 
1452 	/* Add new entry to default folder */
1453 	err = hotlist_add_entry_internal(url, NULL, NULL,
1454 			hl_ctx.default_folder->folder,
1455 			TREE_REL_FIRST_CHILD, &entry);
1456 	if (err != NSERROR_OK)
1457 		return err;
1458 
1459 	/* Ensure default folder is expanded */
1460 	err = treeview_node_expand(hl_ctx.tree, hl_ctx.default_folder->folder);
1461 	if (err != NSERROR_OK)
1462 		return err;
1463 
1464 	return hotlist_schedule_save();
1465 }
1466 
1467 
1468 struct treeview_has_url_walk_ctx {
1469 	nsurl *url;
1470 	bool found;
1471 };
1472 /** Callback for treeview_walk */
hotlist_has_url_walk_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1473 static nserror hotlist_has_url_walk_cb(void *ctx, void *node_data,
1474 		enum treeview_node_type type, bool *abort)
1475 {
1476 	struct treeview_has_url_walk_ctx *tw = ctx;
1477 
1478 	if (type == TREE_NODE_ENTRY) {
1479 		struct hotlist_entry *e = node_data;
1480 
1481 		if (nsurl_compare(e->url, tw->url, NSURL_COMPLETE) == true) {
1482 			/* Found what we're looking for */
1483 			tw->found = true;
1484 			*abort = true;
1485 		}
1486 	}
1487 
1488 	return NSERROR_OK;
1489 }
1490 /* Exported interface, documented in hotlist.h */
hotlist_has_url(nsurl * url)1491 bool hotlist_has_url(nsurl *url)
1492 {
1493 	nserror err;
1494 	struct treeview_has_url_walk_ctx tw = {
1495 		.url = url,
1496 		.found = false
1497 	};
1498 
1499 	if (hl_ctx.built == false)
1500 		return false;
1501 
1502 	err = treeview_walk(hl_ctx.tree, NULL, hotlist_has_url_walk_cb, NULL,
1503 			&tw, TREE_NODE_ENTRY);
1504 	if (err != NSERROR_OK)
1505 		return false;
1506 
1507 	return tw.found;
1508 }
1509 
1510 
1511 struct treeview_remove_url_walk_ctx {
1512 	nsurl *url;
1513 };
1514 /** Callback for treeview_walk */
hotlist_remove_url_walk_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1515 static nserror hotlist_remove_url_walk_cb(void *ctx, void *node_data,
1516 		enum treeview_node_type type, bool *abort)
1517 {
1518 	struct treeview_remove_url_walk_ctx *tw = ctx;
1519 
1520 	if (type == TREE_NODE_ENTRY) {
1521 		struct hotlist_entry *e = node_data;
1522 
1523 		if (nsurl_compare(e->url, tw->url, NSURL_COMPLETE) == true) {
1524 			/* Found what we're looking for: delete it */
1525 			treeview_delete_node(hl_ctx.tree, e->entry,
1526 					TREE_OPTION_NONE);
1527 		}
1528 	}
1529 
1530 	return NSERROR_OK;
1531 }
1532 /* Exported interface, documented in hotlist.h */
hotlist_remove_url(nsurl * url)1533 void hotlist_remove_url(nsurl *url)
1534 {
1535 	nserror err;
1536 	struct treeview_remove_url_walk_ctx tw = {
1537 		.url = url
1538 	};
1539 
1540 	if (hl_ctx.built == false)
1541 		return;
1542 
1543 	err = treeview_walk(hl_ctx.tree, NULL, NULL, hotlist_remove_url_walk_cb,
1544 			&tw, TREE_NODE_ENTRY);
1545 	if (err != NSERROR_OK)
1546 		return;
1547 
1548 	return;
1549 }
1550 
1551 
1552 struct treeview_update_url_walk_ctx {
1553 	nsurl *url;
1554 	const struct url_data *data;
1555 };
1556 /** Callback for treeview_walk */
hotlist_update_url_walk_cb(void * ctx,void * node_data,enum treeview_node_type type,bool * abort)1557 static nserror hotlist_update_url_walk_cb(void *ctx, void *node_data,
1558 		enum treeview_node_type type, bool *abort)
1559 {
1560 	struct treeview_update_url_walk_ctx *tw = ctx;
1561 	struct hotlist_entry *e = node_data;
1562 	nserror err;
1563 
1564 	if (type != TREE_NODE_ENTRY) {
1565 		return NSERROR_OK;
1566 	}
1567 
1568 	if (nsurl_compare(e->url, tw->url, NSURL_COMPLETE) == true) {
1569 		/* Found match: Update the entry data */
1570 		free((void *)e->data[HL_LAST_VISIT].value); /* Eww */
1571 		free((void *)e->data[HL_VISITS].value); /* Eww */
1572 
1573 		if (tw->data == NULL) {
1574 			/* Get the URL data */
1575 			tw->data = urldb_get_url_data(tw->url);
1576 			if (tw->data == NULL) {
1577 				/* No entry in database, so add one */
1578 				urldb_add_url(tw->url);
1579 				/* now attempt to get url data */
1580 				tw->data = urldb_get_url_data(tw->url);
1581 			}
1582 			if (tw->data == NULL) {
1583 				return NSERROR_NOMEM;
1584 			}
1585 		}
1586 
1587 		err = hotlist_create_treeview_field_visits_data(e, tw->data);
1588 		if (err != NSERROR_OK)
1589 			return err;
1590 
1591 		err = treeview_update_node_entry(hl_ctx.tree,
1592 				e->entry, e->data, e);
1593 		if (err != NSERROR_OK)
1594 			return err;
1595 	}
1596 
1597 	return NSERROR_OK;
1598 }
1599 /* Exported interface, documented in hotlist.h */
hotlist_update_url(nsurl * url)1600 void hotlist_update_url(nsurl *url)
1601 {
1602 	nserror err;
1603 	struct treeview_update_url_walk_ctx tw = {
1604 		.url = url,
1605 		.data = NULL
1606 	};
1607 
1608 	if (hl_ctx.built == false)
1609 		return;
1610 
1611 	err = treeview_walk(hl_ctx.tree, NULL, hotlist_update_url_walk_cb, NULL,
1612 			&tw, TREE_NODE_ENTRY);
1613 	if (err != NSERROR_OK)
1614 		return;
1615 
1616 	return;
1617 }
1618 
1619 
1620 /* Exported interface, documented in hotlist.h */
hotlist_add_entry(nsurl * url,const char * title,bool at_y,int y)1621 nserror hotlist_add_entry(nsurl *url, const char *title, bool at_y, int y)
1622 {
1623 	nserror err;
1624 	treeview_node *entry;
1625 	treeview_node *relation;
1626 	enum treeview_relationship rel;
1627 
1628 	if (url == NULL) {
1629 		err = nsurl_create("http://netsurf-browser.org/", &url);
1630 		if (err != NSERROR_OK) {
1631 			return err;
1632 		}
1633 		assert(url != NULL);
1634 	} else {
1635 		nsurl_ref(url);
1636 	}
1637 
1638 	err = treeview_get_relation(hl_ctx.tree, &relation, &rel, at_y, y);
1639 	if (err != NSERROR_OK) {
1640 		nsurl_unref(url);
1641 		return err;
1642 	}
1643 
1644 	err = hotlist_add_entry_internal(url, title, NULL,
1645 			relation, rel, &entry);
1646 	if (err != NSERROR_OK) {
1647 		nsurl_unref(url);
1648 		return err;
1649 	}
1650 
1651 	nsurl_unref(url);
1652 
1653 	return NSERROR_OK;
1654 }
1655 
1656 
1657 /* Exported interface, documented in hotlist.h */
hotlist_add_folder(const char * title,bool at_y,int y)1658 nserror hotlist_add_folder(const char *title, bool at_y, int y)
1659 {
1660 	nserror err;
1661 	struct hotlist_folder *f;
1662 	treeview_node *relation;
1663 	enum treeview_relationship rel;
1664 
1665 	err = treeview_get_relation(hl_ctx.tree, &relation, &rel, at_y, y);
1666 	if (err != NSERROR_OK) {
1667 		return err;
1668 	}
1669 
1670 	err = hotlist_add_folder_internal(title, relation, rel, &f, false);
1671 	if (err != NSERROR_OK) {
1672 		return err;
1673 	}
1674 
1675 	return NSERROR_OK;
1676 }
1677 
1678 
1679 /* Exported interface, documented in hotlist.h */
hotlist_redraw(int x,int y,struct rect * clip,const struct redraw_context * ctx)1680 void hotlist_redraw(int x, int y, struct rect *clip,
1681 		const struct redraw_context *ctx)
1682 {
1683 	treeview_redraw(hl_ctx.tree, x, y, clip, ctx);
1684 }
1685 
1686 
1687 /* Exported interface, documented in hotlist.h */
hotlist_mouse_action(browser_mouse_state mouse,int x,int y)1688 void hotlist_mouse_action(browser_mouse_state mouse, int x, int y)
1689 {
1690 	treeview_mouse_action(hl_ctx.tree, mouse, x, y);
1691 }
1692 
1693 
1694 /* Exported interface, documented in hotlist.h */
hotlist_keypress(uint32_t key)1695 bool hotlist_keypress(uint32_t key)
1696 {
1697 	return treeview_keypress(hl_ctx.tree, key);
1698 }
1699 
1700 
1701 /* Exported interface, documented in hotlist.h */
hotlist_has_selection(void)1702 bool hotlist_has_selection(void)
1703 {
1704 	return treeview_has_selection(hl_ctx.tree);
1705 }
1706 
1707 
1708 /* Exported interface, documented in hotlist.h */
hotlist_get_selection(nsurl ** url,const char ** title)1709 bool hotlist_get_selection(nsurl **url, const char **title)
1710 {
1711 	struct hotlist_entry *e;
1712 	enum treeview_node_type type;
1713 	void *v;
1714 
1715 	type = treeview_get_selection(hl_ctx.tree, &v);
1716 	if (type != TREE_NODE_ENTRY || v == NULL) {
1717 		*url = NULL;
1718 		*title = NULL;
1719 		return false;
1720 	}
1721 
1722 	e = (struct hotlist_entry *)v;
1723 
1724 	*url = e->url;
1725 	*title = e->data[HL_TITLE].value;
1726 	return true;
1727 }
1728 
1729 
1730 /* Exported interface, documented in hotlist.h */
hotlist_edit_selection(void)1731 void hotlist_edit_selection(void)
1732 {
1733 	treeview_edit_selection(hl_ctx.tree);
1734 }
1735 
1736 
1737 /* Exported interface, documented in hotlist.h */
hotlist_expand(bool only_folders)1738 nserror hotlist_expand(bool only_folders)
1739 {
1740 	return treeview_expand(hl_ctx.tree, only_folders);
1741 }
1742 
1743 
1744 /* Exported interface, documented in hotlist.h */
hotlist_contract(bool all)1745 nserror hotlist_contract(bool all)
1746 {
1747 	return treeview_contract(hl_ctx.tree, all);
1748 }
1749 
1750