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