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