You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1014 lines
22 KiB
1014 lines
22 KiB
/*
|
|
* Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "utils/messages.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/utf8.h"
|
|
#include "utils/libdom.h"
|
|
#include "utils/log.h"
|
|
#include "utils/nsurl.h"
|
|
#include "content/urldb.h"
|
|
|
|
#include "desktop/global_history.h"
|
|
#include "desktop/treeview.h"
|
|
#include "netsurf/browser_window.h"
|
|
|
|
#define N_DAYS 28
|
|
#define N_SEC_PER_DAY (60 * 60 * 24)
|
|
|
|
enum global_history_folders {
|
|
GH_TODAY = 0,
|
|
GH_YESTERDAY,
|
|
GH_2_DAYS_AGO,
|
|
GH_3_DAYS_AGO,
|
|
GH_4_DAYS_AGO,
|
|
GH_5_DAYS_AGO,
|
|
GH_6_DAYS_AGO,
|
|
GH_LAST_WEEK,
|
|
GH_2_WEEKS_AGO,
|
|
GH_3_WEEKS_AGO,
|
|
GH_N_FOLDERS
|
|
};
|
|
|
|
enum global_history_fields {
|
|
GH_TITLE,
|
|
GH_URL,
|
|
GH_LAST_VISIT,
|
|
GH_VISITS,
|
|
GH_PERIOD,
|
|
N_FIELDS
|
|
};
|
|
|
|
struct global_history_folder {
|
|
treeview_node *folder;
|
|
struct treeview_field_data data;
|
|
};
|
|
|
|
struct global_history_ctx {
|
|
treeview *tree;
|
|
struct treeview_field_desc fields[N_FIELDS];
|
|
struct global_history_folder folders[GH_N_FOLDERS];
|
|
time_t today;
|
|
int weekday;
|
|
bool built;
|
|
};
|
|
struct global_history_ctx gh_ctx;
|
|
|
|
struct global_history_entry {
|
|
bool user_delete;
|
|
|
|
int slot;
|
|
nsurl *url;
|
|
time_t t;
|
|
treeview_node *entry;
|
|
struct global_history_entry *next;
|
|
struct global_history_entry *prev;
|
|
|
|
struct treeview_field_data data[N_FIELDS - 1];
|
|
};
|
|
struct global_history_entry *gh_list[N_DAYS];
|
|
|
|
|
|
/**
|
|
* Find an entry in the global history
|
|
*
|
|
* \param url The URL to find
|
|
* \return Pointer to history entry, or NULL if not found
|
|
*/
|
|
static struct global_history_entry *global_history_find(nsurl *url)
|
|
{
|
|
int i;
|
|
struct global_history_entry *e;
|
|
|
|
for (i = 0; i < N_DAYS; i++) {
|
|
e = gh_list[i];
|
|
|
|
while (e != NULL) {
|
|
if (nsurl_compare(e->url, url,
|
|
NSURL_COMPLETE) == true) {
|
|
/* Got a match */
|
|
return e;
|
|
}
|
|
e = e->next;
|
|
}
|
|
|
|
}
|
|
|
|
/* No match found */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise the treeview directories
|
|
*
|
|
* \param f Ident for folder to create
|
|
* \return NSERROR_OK on success, appropriate error otherwise
|
|
*/
|
|
static nserror global_history_create_dir(enum global_history_folders f)
|
|
{
|
|
nserror err;
|
|
treeview_node *relation = NULL;
|
|
enum treeview_relationship rel = TREE_REL_FIRST_CHILD;
|
|
const char *label;
|
|
int i;
|
|
|
|
switch (f) {
|
|
case GH_TODAY:
|
|
label = "DateToday";
|
|
break;
|
|
|
|
case GH_YESTERDAY:
|
|
label = "DateYesterday";
|
|
break;
|
|
|
|
case GH_2_DAYS_AGO:
|
|
label = "Date2Days";
|
|
break;
|
|
|
|
case GH_3_DAYS_AGO:
|
|
label = "Date3Days";
|
|
break;
|
|
|
|
case GH_4_DAYS_AGO:
|
|
label = "Date4Days";
|
|
break;
|
|
|
|
case GH_5_DAYS_AGO:
|
|
label = "Date5Days";
|
|
break;
|
|
|
|
case GH_6_DAYS_AGO:
|
|
label = "Date6Days";
|
|
break;
|
|
|
|
case GH_LAST_WEEK:
|
|
label = "Date1Week";
|
|
break;
|
|
|
|
case GH_2_WEEKS_AGO:
|
|
label = "Date2Week";
|
|
break;
|
|
|
|
case GH_3_WEEKS_AGO:
|
|
label = "Date3Week";
|
|
break;
|
|
|
|
default:
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
label = messages_get(label);
|
|
|
|
for (i = f - 1; i >= 0; i--) {
|
|
if (gh_ctx.folders[i].folder != NULL) {
|
|
relation = gh_ctx.folders[i].folder;
|
|
rel = TREE_REL_NEXT_SIBLING;
|
|
break;
|
|
}
|
|
}
|
|
|
|
gh_ctx.folders[f].data.field = gh_ctx.fields[N_FIELDS - 1].field;
|
|
gh_ctx.folders[f].data.value = label;
|
|
gh_ctx.folders[f].data.value_len = strlen(label);
|
|
err = treeview_create_node_folder(gh_ctx.tree,
|
|
&gh_ctx.folders[f].folder,
|
|
relation, rel,
|
|
&gh_ctx.folders[f].data,
|
|
&gh_ctx.folders[f],
|
|
gh_ctx.built ? TREE_OPTION_NONE :
|
|
TREE_OPTION_SUPPRESS_RESIZE |
|
|
TREE_OPTION_SUPPRESS_REDRAW);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the treeview folder for history entires in a particular slot
|
|
*
|
|
* \param parent Updated to parent folder.
|
|
* \param slot Global history slot of entry we want folder node for
|
|
* \return NSERROR_OK on success, appropriate error otherwise
|
|
*/
|
|
static inline nserror global_history_get_parent_treeview_node(
|
|
treeview_node **parent, int slot)
|
|
{
|
|
int folder_index;
|
|
struct global_history_folder *f;
|
|
nserror err;
|
|
|
|
if (slot < 7) {
|
|
folder_index = slot;
|
|
|
|
} else if (slot < 14) {
|
|
folder_index = GH_LAST_WEEK;
|
|
|
|
} else if (slot < 21) {
|
|
folder_index = GH_2_WEEKS_AGO;
|
|
|
|
} else if (slot < N_DAYS) {
|
|
folder_index = GH_3_WEEKS_AGO;
|
|
|
|
} else {
|
|
/* Slot value is invalid */
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
/* Get the folder */
|
|
f = &(gh_ctx.folders[folder_index]);
|
|
|
|
if (f->folder == NULL) {
|
|
err = global_history_create_dir(folder_index);
|
|
if (err != NSERROR_OK) {
|
|
*parent = NULL;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* Return the parent treeview folder */
|
|
*parent = f->folder;
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set a global history entry's data from the url_data.
|
|
*
|
|
* \param e Global history entry to set up
|
|
* \param data Data associated with entry's URL
|
|
* \return NSERROR_OK on success, appropriate error otherwise
|
|
*/
|
|
static nserror global_history_create_treeview_field_data(
|
|
struct global_history_entry *e,
|
|
const struct url_data *data)
|
|
{
|
|
const char *title = (data->title != NULL) ?
|
|
data->title : messages_get("NoTitle");
|
|
char buffer[16];
|
|
struct tm *lvtime;
|
|
char *last_visited = NULL;
|
|
size_t len = 0;
|
|
|
|
e->data[GH_TITLE].field = gh_ctx.fields[GH_TITLE].field;
|
|
e->data[GH_TITLE].value = strdup(title);
|
|
e->data[GH_TITLE].value_len = (e->data[GH_TITLE].value != NULL) ?
|
|
strlen(title) : 0;
|
|
|
|
e->data[GH_URL].field = gh_ctx.fields[GH_URL].field;
|
|
e->data[GH_URL].value = nsurl_access(e->url);
|
|
e->data[GH_URL].value_len = nsurl_length(e->url);
|
|
|
|
if ((lvtime = localtime(&data->last_visit)) != NULL) {
|
|
const size_t lvsize = 256;
|
|
last_visited = malloc(lvsize);
|
|
if (last_visited != NULL) {
|
|
len = strftime(last_visited, lvsize,
|
|
"%a %b %e %H:%M:%S %Y", lvtime);
|
|
}
|
|
}
|
|
|
|
e->data[GH_LAST_VISIT].field = gh_ctx.fields[GH_LAST_VISIT].field;
|
|
e->data[GH_LAST_VISIT].value = last_visited;
|
|
e->data[GH_LAST_VISIT].value_len = len;
|
|
|
|
len = snprintf(buffer, 16, "%u", data->visits);
|
|
if (len == 16) {
|
|
len--;
|
|
buffer[len] = '\0';
|
|
}
|
|
|
|
e->data[GH_VISITS].field = gh_ctx.fields[GH_VISITS].field;
|
|
e->data[GH_VISITS].value = strdup(buffer);
|
|
e->data[GH_VISITS].value_len = len;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Add a global history entry to the treeview
|
|
*
|
|
* \param e entry to add to treeview
|
|
* \param slot global history slot containing entry
|
|
* \return NSERROR_OK on success, or appropriate error otherwise
|
|
*
|
|
* It is assumed that the entry is unique (for its URL) in the global
|
|
* history table
|
|
*/
|
|
static nserror global_history_entry_insert(struct global_history_entry *e,
|
|
int slot)
|
|
{
|
|
nserror err;
|
|
|
|
treeview_node *parent;
|
|
err = global_history_get_parent_treeview_node(&parent, slot);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
err = treeview_create_node_entry(gh_ctx.tree, &(e->entry),
|
|
parent, TREE_REL_FIRST_CHILD, e->data, e,
|
|
gh_ctx.built ? TREE_OPTION_NONE :
|
|
TREE_OPTION_SUPPRESS_RESIZE |
|
|
TREE_OPTION_SUPPRESS_REDRAW);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add an entry to the global history (creates the entry).
|
|
*
|
|
* If the treeview has already been created, the entry will be added to the
|
|
* treeview. Otherwise, the entry will have to be added to the treeview later.
|
|
*
|
|
* When we first create the global history we create it without the treeview, to
|
|
* simplfy sorting the entries.
|
|
*
|
|
* \param url URL for entry to add to history
|
|
* \param slot Global history slot to contain history entry
|
|
* \param data URL data for the entry
|
|
* \param got_treeview Whether the treeview has been created already
|
|
* \return NSERROR_OK on success, or appropriate error otherwise
|
|
*/
|
|
static nserror global_history_add_entry_internal(nsurl *url, int slot,
|
|
const struct url_data *data, bool got_treeview)
|
|
{
|
|
nserror err;
|
|
struct global_history_entry *e;
|
|
|
|
/* Create new local history entry */
|
|
e = malloc(sizeof(struct global_history_entry));
|
|
if (e == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
e->user_delete = false;
|
|
e->slot = slot;
|
|
e->url = nsurl_ref(url);
|
|
e->t = data->last_visit;
|
|
e->entry = NULL;
|
|
e->next = NULL;
|
|
e->prev = NULL;
|
|
|
|
err = global_history_create_treeview_field_data(e, data);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
if (gh_list[slot] == NULL) {
|
|
/* list empty */
|
|
gh_list[slot] = e;
|
|
|
|
} else if (gh_list[slot]->t < e->t) {
|
|
/* Insert at list head */
|
|
e->next = gh_list[slot];
|
|
gh_list[slot]->prev = e;
|
|
gh_list[slot] = e;
|
|
} else {
|
|
struct global_history_entry *prev = gh_list[slot];
|
|
struct global_history_entry *curr = prev->next;
|
|
while (curr != NULL) {
|
|
if (curr->t < e->t) {
|
|
break;
|
|
}
|
|
prev = curr;
|
|
curr = curr->next;
|
|
}
|
|
|
|
/* insert after prev */
|
|
e->next = curr;
|
|
e->prev = prev;
|
|
prev->next = e;
|
|
|
|
if (curr != NULL)
|
|
curr->prev = e;
|
|
}
|
|
|
|
if (got_treeview) {
|
|
err = global_history_entry_insert(e, slot);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Delete a global history entry
|
|
*
|
|
* This does not delete the treeview node, rather it should only be called from
|
|
* the treeview node delete event message.
|
|
*
|
|
* \param e Entry to delete
|
|
*/
|
|
static void global_history_delete_entry_internal(
|
|
struct global_history_entry *e)
|
|
{
|
|
assert(e != NULL);
|
|
assert(e->entry == NULL);
|
|
|
|
/* Unlink */
|
|
if (gh_list[e->slot] == e) {
|
|
/* e is first entry */
|
|
gh_list[e->slot] = e->next;
|
|
|
|
if (e->next != NULL)
|
|
e->next->prev = NULL;
|
|
|
|
} else if (e->next == NULL) {
|
|
/* e is last entry */
|
|
e->prev->next = NULL;
|
|
|
|
} else {
|
|
/* e has an entry before and after */
|
|
e->prev->next = e->next;
|
|
e->next->prev = e->prev;
|
|
}
|
|
|
|
if (e->user_delete) {
|
|
/* User requested delete, so delete from urldb too. */
|
|
urldb_reset_url_visit_data(e->url);
|
|
}
|
|
|
|
/* Destroy fields */
|
|
free((void *)e->data[GH_TITLE].value); /* Eww */
|
|
free((void *)e->data[GH_LAST_VISIT].value); /* Eww */
|
|
free((void *)e->data[GH_VISITS].value); /* Eww */
|
|
nsurl_unref(e->url);
|
|
|
|
/* Destroy entry */
|
|
free(e);
|
|
}
|
|
|
|
/**
|
|
* Internal routine to actually perform global history addition
|
|
*
|
|
* \param url The URL to add
|
|
* \param data URL data associated with URL
|
|
* \return true (for urldb_iterate_entries)
|
|
*/
|
|
static bool global_history_add_entry(nsurl *url,
|
|
const struct url_data *data)
|
|
{
|
|
int slot;
|
|
time_t visit_date;
|
|
time_t earliest_date = gh_ctx.today - (N_DAYS - 1) * N_SEC_PER_DAY;
|
|
bool got_treeview = gh_ctx.tree != NULL;
|
|
|
|
assert((url != NULL) && (data != NULL));
|
|
|
|
visit_date = data->last_visit;
|
|
|
|
/* Find day array slot for entry */
|
|
if (visit_date >= gh_ctx.today) {
|
|
slot = 0;
|
|
} else if (visit_date >= earliest_date) {
|
|
slot = (gh_ctx.today - visit_date) / N_SEC_PER_DAY + 1;
|
|
} else {
|
|
/* too old */
|
|
return true;
|
|
}
|
|
|
|
if (got_treeview == true) {
|
|
/* The treeview for global history already exists */
|
|
struct global_history_entry *e;
|
|
|
|
/* Delete any existing entry for this URL */
|
|
e = global_history_find(url);
|
|
if (e != NULL) {
|
|
treeview_delete_node(gh_ctx.tree, e->entry,
|
|
TREE_OPTION_SUPPRESS_REDRAW |
|
|
TREE_OPTION_SUPPRESS_RESIZE);
|
|
}
|
|
}
|
|
|
|
if (global_history_add_entry_internal(url, slot, data,
|
|
got_treeview) != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Initialise the treeview entry feilds
|
|
*
|
|
* \return NSERROR_OK on success, or appropriate error otherwise
|
|
*/
|
|
static nserror global_history_initialise_entry_fields(void)
|
|
{
|
|
int i;
|
|
const char *label;
|
|
|
|
for (i = 0; i < N_FIELDS; i++)
|
|
gh_ctx.fields[i].field = NULL;
|
|
|
|
gh_ctx.fields[GH_TITLE].flags = TREE_FLAG_DEFAULT;
|
|
label = "TreeviewLabelTitle";
|
|
label = messages_get(label);
|
|
if (lwc_intern_string(label, strlen(label),
|
|
&gh_ctx.fields[GH_TITLE].field) !=
|
|
lwc_error_ok) {
|
|
goto error;
|
|
}
|
|
|
|
gh_ctx.fields[GH_URL].flags =
|
|
TREE_FLAG_COPY_TEXT |
|
|
TREE_FLAG_SEARCHABLE;
|
|
label = "TreeviewLabelURL";
|
|
label = messages_get(label);
|
|
if (lwc_intern_string(label, strlen(label),
|
|
&gh_ctx.fields[GH_URL].field) !=
|
|
lwc_error_ok) {
|
|
goto error;
|
|
}
|
|
|
|
gh_ctx.fields[GH_LAST_VISIT].flags = TREE_FLAG_SHOW_NAME;
|
|
label = "TreeviewLabelLastVisit";
|
|
label = messages_get(label);
|
|
if (lwc_intern_string(label, strlen(label),
|
|
&gh_ctx.fields[GH_LAST_VISIT].field) !=
|
|
lwc_error_ok) {
|
|
goto error;
|
|
}
|
|
|
|
gh_ctx.fields[GH_VISITS].flags = TREE_FLAG_SHOW_NAME;
|
|
label = "TreeviewLabelVisits";
|
|
label = messages_get(label);
|
|
if (lwc_intern_string(label, strlen(label),
|
|
&gh_ctx.fields[GH_VISITS].field) !=
|
|
lwc_error_ok) {
|
|
goto error;
|
|
}
|
|
|
|
gh_ctx.fields[GH_PERIOD].flags = TREE_FLAG_DEFAULT;
|
|
label = "TreeviewLabelPeriod";
|
|
label = messages_get(label);
|
|
if (lwc_intern_string(label, strlen(label),
|
|
&gh_ctx.fields[GH_PERIOD].field) !=
|
|
lwc_error_ok) {
|
|
return false;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
|
|
error:
|
|
for (i = 0; i < N_FIELDS; i++)
|
|
if (gh_ctx.fields[i].field != NULL)
|
|
lwc_string_unref(gh_ctx.fields[i].field);
|
|
|
|
return NSERROR_UNKNOWN;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise the time
|
|
*
|
|
* \return NSERROR_OK on success, or appropriate error otherwise
|
|
*/
|
|
static nserror global_history_initialise_time(void)
|
|
{
|
|
struct tm *full_time;
|
|
time_t t;
|
|
|
|
/* get the current time */
|
|
t = time(NULL);
|
|
if (t == -1) {
|
|
NSLOG(netsurf, INFO, "time info unaviable");
|
|
return NSERROR_UNKNOWN;
|
|
}
|
|
|
|
/* get the time at the start of today */
|
|
full_time = localtime(&t);
|
|
full_time->tm_sec = 0;
|
|
full_time->tm_min = 0;
|
|
full_time->tm_hour = 0;
|
|
t = mktime(full_time);
|
|
if (t == -1) {
|
|
NSLOG(netsurf, INFO, "mktime failed");
|
|
return NSERROR_UNKNOWN;
|
|
}
|
|
|
|
gh_ctx.today = t;
|
|
gh_ctx.weekday = full_time->tm_wday;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise the treeview entries
|
|
*
|
|
* \return NSERROR_OK on success, or appropriate error otherwise
|
|
*/
|
|
static nserror global_history_init_entries(void)
|
|
{
|
|
int i;
|
|
nserror err;
|
|
|
|
/* Itterate over all global history data, inserting it into treeview */
|
|
for (i = 0; i < N_DAYS; i++) {
|
|
struct global_history_entry *l = NULL;
|
|
struct global_history_entry *e = gh_list[i];
|
|
|
|
/* Insert in reverse order; find last */
|
|
while (e != NULL) {
|
|
l = e;
|
|
e = e->next;
|
|
}
|
|
|
|
/* Insert the entries into the treeview */
|
|
while (l != NULL) {
|
|
err = global_history_entry_insert(l, i);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
l = l->prev;
|
|
}
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
static nserror global_history_tree_node_folder_cb(
|
|
struct treeview_node_msg msg, void *data)
|
|
{
|
|
struct global_history_folder *f = data;
|
|
|
|
switch (msg.msg) {
|
|
case TREE_MSG_NODE_DELETE:
|
|
f->folder = NULL;
|
|
break;
|
|
|
|
case TREE_MSG_NODE_EDIT:
|
|
break;
|
|
|
|
case TREE_MSG_NODE_LAUNCH:
|
|
break;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
static nserror
|
|
global_history_tree_node_entry_cb(struct treeview_node_msg msg, void *data)
|
|
{
|
|
struct global_history_entry *e = data;
|
|
nserror ret = NSERROR_OK;
|
|
|
|
switch (msg.msg) {
|
|
case TREE_MSG_NODE_DELETE:
|
|
e->entry = NULL;
|
|
e->user_delete = msg.data.delete.user;
|
|
global_history_delete_entry_internal(e);
|
|
break;
|
|
|
|
case TREE_MSG_NODE_EDIT:
|
|
break;
|
|
|
|
case TREE_MSG_NODE_LAUNCH:
|
|
{
|
|
struct browser_window *existing = NULL;
|
|
enum browser_window_create_flags flags = BW_CREATE_HISTORY;
|
|
|
|
/* TODO: Set existing to window that new tab appears in */
|
|
|
|
if (msg.data.node_launch.mouse &
|
|
(BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2) ||
|
|
existing == NULL) {
|
|
/* Shift or Ctrl launch, open in new window rather
|
|
* than tab. */
|
|
/* TODO: flags ^= BW_CREATE_TAB; */
|
|
}
|
|
|
|
ret = browser_window_create(flags, e->url, NULL,
|
|
existing, NULL);
|
|
}
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
struct treeview_callback_table gh_tree_cb_t = {
|
|
.folder = global_history_tree_node_folder_cb,
|
|
.entry = global_history_tree_node_entry_cb
|
|
};
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
nserror global_history_init(void *core_window_handle)
|
|
{
|
|
nserror err;
|
|
|
|
err = treeview_init();
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
NSLOG(netsurf, INFO, "Loading global history");
|
|
|
|
/* Init. global history treeview time */
|
|
err = global_history_initialise_time();
|
|
if (err != NSERROR_OK) {
|
|
gh_ctx.tree = NULL;
|
|
return err;
|
|
}
|
|
|
|
/* Init. global history treeview entry fields */
|
|
err = global_history_initialise_entry_fields();
|
|
if (err != NSERROR_OK) {
|
|
gh_ctx.tree = NULL;
|
|
return err;
|
|
}
|
|
|
|
/* Load the entries */
|
|
urldb_iterate_entries(global_history_add_entry);
|
|
|
|
/* Create the global history treeview */
|
|
err = treeview_create(&gh_ctx.tree, &gh_tree_cb_t,
|
|
N_FIELDS, gh_ctx.fields,
|
|
core_window_handle,
|
|
TREEVIEW_NO_MOVES | TREEVIEW_DEL_EMPTY_DIRS |
|
|
TREEVIEW_SEARCHABLE);
|
|
if (err != NSERROR_OK) {
|
|
gh_ctx.tree = NULL;
|
|
return err;
|
|
}
|
|
|
|
/* Ensure there is a folder for today */
|
|
err = global_history_create_dir(GH_TODAY);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
/* Add the history to the treeview */
|
|
err = global_history_init_entries();
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
/* Expand the "Today" folder node */
|
|
err = treeview_node_expand(gh_ctx.tree,
|
|
gh_ctx.folders[GH_TODAY].folder);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
/* History tree is built
|
|
* We suppress the treeview height callback on entry insertion before
|
|
* the treeview is built. */
|
|
gh_ctx.built = true;
|
|
|
|
/* Inform client of window height */
|
|
treeview_get_height(gh_ctx.tree);
|
|
|
|
NSLOG(netsurf, INFO, "Loaded global history");
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
nserror global_history_fini(void)
|
|
{
|
|
int i;
|
|
nserror err;
|
|
|
|
NSLOG(netsurf, INFO, "Finalising global history");
|
|
|
|
gh_ctx.built = false;
|
|
|
|
/* Destroy the global history treeview */
|
|
err = treeview_destroy(gh_ctx.tree);
|
|
gh_ctx.tree = NULL;
|
|
|
|
/* Free global history treeview entry fields */
|
|
for (i = 0; i < N_FIELDS; i++)
|
|
if (gh_ctx.fields[i].field != NULL)
|
|
lwc_string_unref(gh_ctx.fields[i].field);
|
|
|
|
err = treeview_fini();
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
NSLOG(netsurf, INFO, "Finalised global history");
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
nserror global_history_add(nsurl *url)
|
|
{
|
|
const struct url_data *data;
|
|
|
|
/* If we don't have a global history at the moment, just return OK */
|
|
if (gh_ctx.tree == NULL)
|
|
return NSERROR_OK;
|
|
|
|
data = urldb_get_url_data(url);
|
|
if (data == NULL) {
|
|
NSLOG(netsurf, INFO,
|
|
"Can't add URL to history that's not present in urldb.");
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
global_history_add_entry(url, data);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
struct treeview_export_walk_ctx {
|
|
FILE *fp;
|
|
};
|
|
/** Callback for treeview_walk node entering */
|
|
static nserror global_history_export_enter_cb(void *ctx, void *node_data,
|
|
enum treeview_node_type type, bool *abort)
|
|
{
|
|
struct treeview_export_walk_ctx *tw = ctx;
|
|
nserror ret;
|
|
|
|
if (type == TREE_NODE_ENTRY) {
|
|
struct global_history_entry *e = node_data;
|
|
char *t_text;
|
|
char *u_text;
|
|
|
|
ret = utf8_to_html(e->data[GH_TITLE].value, "iso-8859-1",
|
|
e->data[GH_TITLE].value_len, &t_text);
|
|
if (ret != NSERROR_OK)
|
|
return NSERROR_SAVE_FAILED;
|
|
|
|
ret = utf8_to_html(e->data[GH_URL].value, "iso-8859-1",
|
|
e->data[GH_URL].value_len, &u_text);
|
|
if (ret != NSERROR_OK) {
|
|
free(t_text);
|
|
return NSERROR_SAVE_FAILED;
|
|
}
|
|
|
|
fprintf(tw->fp, "<li><a href=\"%s\">%s</a></li>\n",
|
|
u_text, t_text);
|
|
|
|
free(t_text);
|
|
free(u_text);
|
|
|
|
} else if (type == TREE_NODE_FOLDER) {
|
|
struct global_history_folder *f = node_data;
|
|
char *f_text;
|
|
|
|
ret = utf8_to_html(f->data.value, "iso-8859-1",
|
|
f->data.value_len, &f_text);
|
|
if (ret != NSERROR_OK)
|
|
return NSERROR_SAVE_FAILED;
|
|
|
|
fprintf(tw->fp, "<li><h4>%s</h4>\n<ul>\n", f_text);
|
|
|
|
free(f_text);
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
/** Callback for treeview_walk node leaving */
|
|
static nserror global_history_export_leave_cb(void *ctx, void *node_data,
|
|
enum treeview_node_type type, bool *abort)
|
|
{
|
|
struct treeview_export_walk_ctx *tw = ctx;
|
|
|
|
if (type == TREE_NODE_FOLDER) {
|
|
fputs("</ul></li>\n", tw->fp);
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
/* Exported interface, documented in global_history.h */
|
|
nserror global_history_export(const char *path, const char *title)
|
|
{
|
|
struct treeview_export_walk_ctx tw;
|
|
nserror err;
|
|
FILE *fp;
|
|
|
|
fp = fopen(path, "w");
|
|
if (fp == NULL)
|
|
return NSERROR_SAVE_FAILED;
|
|
|
|
if (title == NULL)
|
|
title = "NetSurf Browsing History";
|
|
|
|
fputs("<!DOCTYPE html "
|
|
"PUBLIC \"//W3C/DTD HTML 4.01//EN\" "
|
|
"\"http://www.w3.org/TR/html4/strict.dtd\">\n", fp);
|
|
fputs("<html>\n<head>\n", fp);
|
|
fputs("<meta http-equiv=\"Content-Type\" "
|
|
"content=\"text/html; charset=iso-8859-1\">\n", fp);
|
|
fprintf(fp, "<title>%s</title>\n", title);
|
|
fputs("</head>\n<body>\n<ul>\n", fp);
|
|
|
|
tw.fp = fp;
|
|
err = treeview_walk(gh_ctx.tree, NULL,
|
|
global_history_export_enter_cb,
|
|
global_history_export_leave_cb,
|
|
&tw, TREE_NODE_ENTRY | TREE_NODE_FOLDER);
|
|
if (err != NSERROR_OK)
|
|
return err;
|
|
|
|
fputs("</ul>\n</body>\n</html>\n", fp);
|
|
|
|
fclose(fp);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
void global_history_redraw(int x, int y, struct rect *clip,
|
|
const struct redraw_context *ctx)
|
|
{
|
|
treeview_redraw(gh_ctx.tree, x, y, clip, ctx);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
void global_history_mouse_action(browser_mouse_state mouse, int x, int y)
|
|
{
|
|
treeview_mouse_action(gh_ctx.tree, mouse, x, y);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
bool global_history_keypress(uint32_t key)
|
|
{
|
|
return treeview_keypress(gh_ctx.tree, key);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
bool global_history_has_selection(void)
|
|
{
|
|
return treeview_has_selection(gh_ctx.tree);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
bool global_history_get_selection(nsurl **url, const char **title)
|
|
{
|
|
struct global_history_entry *e;
|
|
enum treeview_node_type type;
|
|
void *v;
|
|
|
|
type = treeview_get_selection(gh_ctx.tree, &v);
|
|
if (type != TREE_NODE_ENTRY || v == NULL) {
|
|
*url = NULL;
|
|
*title = NULL;
|
|
return false;
|
|
}
|
|
|
|
e = (struct global_history_entry *)v;
|
|
|
|
*url = e->url;
|
|
*title = e->data[GH_TITLE].value;
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
nserror global_history_expand(bool only_folders)
|
|
{
|
|
return treeview_expand(gh_ctx.tree, only_folders);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in global_history.h */
|
|
nserror global_history_contract(bool all)
|
|
{
|
|
return treeview_contract(gh_ctx.tree, all);
|
|
}
|
|
|