/*
 * Copyright 2005 Richard Wilson <info@tinct.net>
 * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
 *
 * 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/>.
 */

/** \file
 * Creation of URL nodes with use of trees (implementation)
 */


#include <assert.h>
#include <ctype.h>

#include <dom/dom.h>
#include <dom/bindings/hubbub/parser.h>

#include "content/content.h"
#include "content/hlcache.h"
#include "content/urldb.h"
#include "desktop/browser.h"
#include "desktop/options.h"
#include "desktop/tree_url_node.h"
#include "utils/corestrings.h"
#include "utils/libdom.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utf8.h"
#include "utils/utils.h"

/** Flags for each type of url tree node. */
enum tree_element_url {
	TREE_ELEMENT_URL = 0x01,
	TREE_ELEMENT_LAST_VISIT = 0x02,
	TREE_ELEMENT_VISITS = 0x03,
	TREE_ELEMENT_THUMBNAIL = 0x04,
};

#define MAX_ICON_NAME_LEN 256

static bool initialised = false;

static hlcache_handle *folder_icon;

struct icon_entry {
	content_type type;
	hlcache_handle *icon;
};

struct icon_entry icon_table[] = {
	{CONTENT_HTML, NULL},
	{CONTENT_TEXTPLAIN, NULL},
	{CONTENT_CSS, NULL},
	{CONTENT_IMAGE, NULL},
	{CONTENT_NONE, NULL},

	/* this serves as a sentinel */
	{CONTENT_HTML, NULL}
};

static uint32_t tun_users = 0;

void tree_url_node_init(const char *folder_icon_name)
{
	struct icon_entry *entry;
	char icon_name[MAX_ICON_NAME_LEN];
	
	tun_users++;
	
	if (initialised)
		return;
	initialised = true;

	folder_icon = tree_load_icon(folder_icon_name);

	entry = icon_table;
	do {

		tree_icon_name_from_content_type(icon_name, entry->type);
		entry->icon = tree_load_icon(icon_name);

		++entry;
	} while (entry->type != CONTENT_HTML);
}


void tree_url_node_cleanup()
{
	struct icon_entry *entry;
	
	tun_users--;
	
	if (tun_users > 0)
		return;
	
	if (!initialised)
		return;
	initialised = false;
	
	hlcache_handle_release(folder_icon);
	
	entry = icon_table;
	do {
		hlcache_handle_release(entry->icon);
		++entry;
	} while (entry->type != CONTENT_HTML);
}

/**
 * Creates a tree entry for a URL, and links it into the tree
 *
 * \param parent     the node to link to
 * \param url        the URL (copied)
 * \param data	     the URL data to use
 * \param title	     the custom title to use
 * \return the node created, or NULL for failure
 */
struct node *tree_create_URL_node(struct tree *tree, struct node *parent,
		nsurl *url, const char *title,
		tree_node_user_callback user_callback, void *callback_data)
{
	struct node *node;
	struct node_element *element;
	char *text_cp, *squashed;

	squashed = squash_whitespace(title ? title : nsurl_access(url));
	text_cp = strdup(squashed);
	if (text_cp == NULL) {
		LOG(("malloc failed"));
		warn_user("NoMemory", 0);
		return NULL;
	}
	free(squashed);
	node = tree_create_leaf_node(tree, parent, text_cp, true, false,
				     false);
	if (node == NULL) {
		free(text_cp);
		return NULL;
	}

	if (user_callback != NULL)
		tree_set_node_user_callback(node, user_callback,
					    callback_data);

	tree_create_node_element(node, NODE_ELEMENT_BITMAP,
				 TREE_ELEMENT_THUMBNAIL, false);
	tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS,
				 false);
	tree_create_node_element(node, NODE_ELEMENT_TEXT,
				 TREE_ELEMENT_LAST_VISIT, false);
	element = tree_create_node_element(node, NODE_ELEMENT_TEXT,
					   TREE_ELEMENT_URL, true);
	if (element != NULL) {
		text_cp = strdup(nsurl_access(url));
		if (text_cp == NULL) {
			tree_delete_node(tree, node, false);
			LOG(("malloc failed"));
			warn_user("NoMemory", 0);
			return NULL;
		}
		tree_update_node_element(tree, element, text_cp, NULL);
	}

	return node;
}


/**
 * Creates a read only tree entry for a URL, and links it into the tree.
 *
 * \param parent      the node to link to
 * \param url         the URL
 * \param data	      the URL data to use
 * \return the node created, or NULL for failure
 */
struct node *tree_create_URL_node_readonly(struct tree *tree, 
		struct node *parent, nsurl *url, 
		const struct url_data *data, 
		tree_node_user_callback user_callback, void *callback_data)
{
	struct node *node;
	struct node_element *element;
	char *title;

	assert(url && data);

	if (data->title != NULL) {
		title = strdup(data->title);
	} else {
		title = strdup(nsurl_access(url));
	}

	if (title == NULL)
		return NULL;

	node = tree_create_leaf_node(tree, parent, title, false, false, false);
	if (node == NULL) {
		free(title);
		return NULL;
	}

	if (user_callback != NULL) {
		tree_set_node_user_callback(node, user_callback,
					    callback_data);
	}

	tree_create_node_element(node, NODE_ELEMENT_BITMAP,
				 TREE_ELEMENT_THUMBNAIL, false);
	tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS,
				 false);
	tree_create_node_element(node, NODE_ELEMENT_TEXT,
				 TREE_ELEMENT_LAST_VISIT, false);
	element = tree_create_node_element(node, NODE_ELEMENT_TEXT,
					   TREE_ELEMENT_URL, false);
	if (element != NULL) {
		tree_update_node_element(tree, element, nsurl_access(url),
				NULL);
	}

	tree_update_URL_node(tree, node, url, data);

	return node;
}


/**
 * Updates the node details for a URL node.
 *
 * \param node  the node to update
 */
void tree_update_URL_node(struct tree *tree, struct node *node,
		nsurl *url, const struct url_data *data)
{
	struct node_element *element;
	struct bitmap *bitmap = NULL;
	struct icon_entry *entry;
	char *text_cp;

	assert(node != NULL);

	element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
	if (element == NULL)
		return;

	if (data != NULL) {
		if (data->title == NULL)
			urldb_set_url_title(url, nsurl_access(url));

		if (data->title == NULL)
			return;

		element = tree_node_find_element(node, TREE_ELEMENT_TITLE,
						 NULL);
			
		text_cp = strdup(data->title);
		if (text_cp == NULL) {
			LOG(("malloc failed"));
			warn_user("NoMemory", 0);
			return;
		}
		tree_update_node_element(tree, element,	text_cp, NULL);
	} else {
		data = urldb_get_url_data(url);
		if (data == NULL)
			return;
	}

	entry = icon_table;
	do {
		if (entry->type == data->type) {
			if (entry->icon != NULL)
				tree_set_node_icon(tree, node, entry->icon);
			break;
		}
		++entry;
	} while (entry->type != CONTENT_HTML);

	/* update last visit text */
	element = tree_node_find_element(node, TREE_ELEMENT_LAST_VISIT, element);
	tree_update_element_text(tree,
		element, 
		messages_get_buff("TreeLast",
			(data->last_visit > 0) ?
			ctime((time_t *)&data->last_visit) :
			messages_get("TreeUnknown")));


	/* update number of visits text */
	element = tree_node_find_element(node, TREE_ELEMENT_VISITS, element);
	tree_update_element_text(tree,
		element, 
		messages_get_buff("TreeVisits", data->visits));


	/* update thumbnail */
	element = tree_node_find_element(node, TREE_ELEMENT_THUMBNAIL, element);
	if (element != NULL) {
		bitmap = urldb_get_thumbnail(url);

		if (bitmap != NULL) {
			tree_update_node_element(tree, element, NULL, bitmap);
		}
	}
}


const char *tree_url_node_get_title(struct node *node)
{
	struct node_element *element;
	element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL);
	if (element == NULL)
		return NULL;
	return tree_node_element_get_text(element);
}


const char *tree_url_node_get_url(struct node *node)
{
	struct node_element *element;
	element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
	if (element == NULL)
		return NULL;
	return tree_node_element_get_text(element);
}

void tree_url_node_edit_title(struct tree *tree, struct node *node)
{
	struct node_element *element;
	element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL);
	tree_start_edit(tree, element);
}

void tree_url_node_edit_url(struct tree *tree, struct node *node)
{
	struct node_element *element;
	element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
	tree_start_edit(tree, element);
}

node_callback_resp tree_url_node_callback(void *user_data,
					  struct node_msg_data *msg_data)
{
	struct tree *tree;
	struct node_element *element;
	nsurl *url;
	nserror error;
	const char *text;
	char *norm_text;
	const struct url_data *data;

	/** @todo memory leaks on non-shared folder deletion. */
	switch (msg_data->msg) {
	case NODE_DELETE_ELEMENT_TXT:
		switch (msg_data->flag) {
			/* only history is using non-editable url
			 * elements so only history deletion will run
			 * this code
			 */
		case TREE_ELEMENT_URL:
			/* reset URL characteristics */
			error = nsurl_create(msg_data->data.text, &url);
			if (error != NSERROR_OK) {
				warn_user("NoMemory", 0);
				return NODE_CALLBACK_REJECT;
			}
			urldb_reset_url_visit_data(url);
			nsurl_unref(url);
			return NODE_CALLBACK_HANDLED;
		case TREE_ELEMENT_TITLE:
			return NODE_CALLBACK_HANDLED;
		}
		break;
	case NODE_DELETE_ELEMENT_IMG:
		if (msg_data->flag == TREE_ELEMENT_THUMBNAIL ||
		    msg_data->flag == TREE_ELEMENT_TITLE)
			return NODE_CALLBACK_HANDLED;
		break;
	case NODE_LAUNCH:
		element = tree_node_find_element(msg_data->node,
						 TREE_ELEMENT_URL, NULL);
		if (element != NULL) {
			nserror error;
			enum browser_window_nav_flags flags;

			text = tree_node_element_get_text(element);

			error = nsurl_create(text, &url);
			if (error == NSERROR_OK) {
				struct browser_window *clone = NULL;

				flags = BROWSER_WINDOW_VERIFIABLE |
					BROWSER_WINDOW_HISTORY;
				if (msg_data->flag == TREE_ELEMENT_LAUNCH_IN_TABS) {
					flags |= BROWSER_WINDOW_TAB;
					clone = msg_data->data.bw;
				}
				error = browser_window_create(flags,
							      url,
							      NULL,
							      clone,
							      &msg_data->data.bw);
				nsurl_unref(url);
			}
			if (error != NSERROR_OK) {
				warn_user(messages_get_errorcode(error), 0);
			}

			return NODE_CALLBACK_HANDLED;
		}
		break;

	case NODE_ELEMENT_EDIT_FINISHING:

		text = msg_data->data.text;

		if (msg_data->flag == TREE_ELEMENT_URL) {
			size_t len;
			error = nsurl_create(text, &url);
			if (error != NSERROR_OK) {
				warn_user("NoMemory", 0);
				return NODE_CALLBACK_REJECT;
			}
			error = nsurl_get(url, NSURL_WITH_FRAGMENT,
					&norm_text, &len);
			if (error != NSERROR_OK) {
				warn_user("NoMemory", 0);
				return NODE_CALLBACK_REJECT;
			}

			msg_data->data.text = norm_text;

			data = urldb_get_url_data(url);
			if (data == NULL) {
				urldb_add_url(url);
				urldb_set_url_persistence(url, true);
				data = urldb_get_url_data(url);
				if (data == NULL) {
					nsurl_unref(url);
					return NODE_CALLBACK_REJECT;
				}
			}
			tree = user_data;
			tree_update_URL_node(tree, msg_data->node,
					     url, NULL);
			nsurl_unref(url);
		}
		else if (msg_data->flag == TREE_ELEMENT_TITLE) {
			while (isspace(*text))
				text++;
			norm_text = strdup(text);
			if (norm_text == NULL) {
				LOG(("malloc failed"));
				warn_user("NoMemory", 0);
				return NODE_CALLBACK_REJECT;
			}
			/* don't allow zero length entry text, return
			   false */
			if (norm_text[0] == '\0') {
				warn_user("NoNameError", 0);
				msg_data->data.text = NULL;
				return NODE_CALLBACK_CONTINUE;
			}
			msg_data->data.text = norm_text;
		}

		return NODE_CALLBACK_HANDLED;
	default:
		break;
	}
	return NODE_CALLBACK_NOT_HANDLED;
}

typedef struct {
	struct tree *tree;
	struct node *directory;
	tree_node_user_callback callback;
	void *callback_data;
	bool last_was_h4;
	dom_string *title;
} tree_url_load_ctx;

static void tree_url_load_directory(dom_node *ul, tree_url_load_ctx *ctx);

/**
 * Parse an entry represented as a li.
 *
 * \param  li         DOM node for parsed li
 * \param  directory  directory to add this entry to
 */
static void tree_url_load_entry(dom_node *li, tree_url_load_ctx *ctx)
{
	dom_node *a;
	dom_string *title1;
	dom_string *url1;
	char *title, *url2;
	nsurl *url;
	const struct url_data *data;
	struct node *entry;
	dom_exception derror;
	nserror error;

	/* The li must contain an "a" element */
	a = libdom_find_first_element(li, corestring_lwc_a);
	if (a == NULL) {
		warn_user("TreeLoadError", "(Missing <a> in <li>)");
		return;
	}

	derror = dom_node_get_text_content(a, &title1);
	if (derror != DOM_NO_ERR) {
		warn_user("TreeLoadError", "(No title)");
		dom_node_unref(a);
		return;
	}

	derror = dom_element_get_attribute(a, corestring_dom_href, &url1);
	if (derror != DOM_NO_ERR || url1 == NULL) {
		warn_user("TreeLoadError", "(No URL)");
		dom_string_unref(title1);
		dom_node_unref(a);
		return;
	}

	if (title1 != NULL) {
		title = strndup(dom_string_data(title1),
				dom_string_byte_length(title1));
		dom_string_unref(title1);
	} else {
		title = strdup("");
	}
	if (title == NULL) {
		warn_user("NoMemory", NULL);
		dom_string_unref(url1);
		dom_node_unref(a);
		return;
	}

	/* We're loading external input.
	 * This may be garbage, so attempt to normalise via nsurl
	 */
	url2 = strndup(dom_string_data(url1), dom_string_byte_length(url1));
	if (url2 == NULL) {
		warn_user("NoMemory", NULL);
		free(title);
		dom_string_unref(url1);
		dom_node_unref(a);
		return;
	}

	dom_string_unref(url1);

	error = nsurl_create(url2, &url);

	free(url2);

	if (error != NSERROR_OK) {
		LOG(("Failed normalising '%s'", url2));

		warn_user("NoMemory", NULL);

		free(title);
		dom_node_unref(a);

		return;
	}

	data = urldb_get_url_data(url);
	if (data == NULL) {
		/* No entry in database, so add one */
		urldb_add_url(url);
		/* now attempt to get url data */
		data = urldb_get_url_data(url);
	}
	if (data == NULL) {
		nsurl_unref(url);
		free(title);
		dom_node_unref(a);

		return;
	}

	/* Make this URL persistent */
	urldb_set_url_persistence(url, true);

	/* Force the title in the hotlist */
	urldb_set_url_title(url, title);

	entry = tree_create_URL_node(ctx->tree, ctx->directory, url, title,
				     ctx->callback, ctx->callback_data);

 	if (entry == NULL) {
 		/** \todo why isn't this fatal? */
 		warn_user("NoMemory", 0);
 	} else {
		tree_update_URL_node(ctx->tree, entry, url, data);
	}

	nsurl_unref(url);
	free(title);
	dom_node_unref(a);
}

static bool tree_url_load_directory_cb(dom_node *node, void *ctx)
{
	tree_url_load_ctx *tctx = ctx;
	dom_string *name;
	dom_exception error;

	/* The ul may contain entries as a li, or directories as
	 * an h4 followed by a ul. Non-element nodes may be present
	 * (eg. text, comments), and are ignored. */

	error = dom_node_get_node_name(node, &name);
	if (error != DOM_NO_ERR || name == NULL)
		return false;

	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_li)) {
		/* entry */
		tree_url_load_entry(node, tctx);
		tctx->last_was_h4 = false;
	} else if (dom_string_caseless_lwc_isequal(name, corestring_lwc_h4)) {
		/* directory (a) */
		dom_string *title;

		error = dom_node_get_text_content(node, &title);
		if (error != DOM_NO_ERR || title == NULL) {
			warn_user("TreeLoadError", "(Empty <h4> "
					"or memory exhausted.)");
			dom_string_unref(name);
			return false;
		}

		if (tctx->title != NULL)
			dom_string_unref(tctx->title);
		tctx->title = title;
		tctx->last_was_h4 = true;
	} else if (tctx->last_was_h4 && dom_string_caseless_lwc_isequal(name, 
			corestring_lwc_ul)) {
		/* directory (b) */
		dom_string *id;
		bool dir_is_default;
		struct node *dir;
		char *title;
		tree_url_load_ctx new_ctx;

		error = dom_element_get_attribute(node, corestring_dom_id, &id);
		if (error != DOM_NO_ERR) {
			dom_string_unref(name);
			return false;
		}

		if (id != NULL) {
			dir_is_default = dom_string_caseless_lwc_isequal(id,
					corestring_lwc_default);

			dom_string_unref(id);
		} else {
			dir_is_default = false;
		}

		title = strndup(dom_string_data(tctx->title),
				dom_string_byte_length(tctx->title));
		if (title == NULL) {
			dom_string_unref(name);
			return false;
		}

		dir = tree_create_folder_node(tctx->tree, tctx->directory,
				title, true, false, false);
		if (dir == NULL) {
			dom_string_unref(name);
			return false;
		}

		if (dir_is_default)
			tree_set_default_folder_node(tctx->tree, dir);

		if (tctx->callback != NULL)
			tree_set_node_user_callback(dir, tctx->callback,
						    tctx->callback_data);

		if (folder_icon != NULL)
			tree_set_node_icon(tctx->tree, dir, folder_icon);

		new_ctx.tree = tctx->tree;
		new_ctx.directory = dir;
		new_ctx.callback = tctx->callback;
		new_ctx.callback_data = tctx->callback_data;
		new_ctx.last_was_h4 = false;
		new_ctx.title = NULL;

		tree_url_load_directory(node, &new_ctx);

		if (new_ctx.title != NULL) {
			dom_string_unref(new_ctx.title);
			new_ctx.title = NULL;
		}
		tctx->last_was_h4 = false;
	} else {
		tctx->last_was_h4 = false;
	}

	dom_string_unref(name);

	return true;
}

/**
 * Parse a directory represented as a ul.
 *
 * \param  ul         DOM node for parsed ul
 * \param  directory  directory to add this directory to
 */
static void tree_url_load_directory(dom_node *ul, tree_url_load_ctx *ctx)
{
	assert(ul != NULL);
	assert(ctx != NULL);
	assert(ctx->directory != NULL);

	libdom_iterate_child_elements(ul, tree_url_load_directory_cb, ctx);
}

/**
 * Loads an url tree from a specified file.
 *
 * \param  filename  	name of file to read
 * \param  tree		empty tree which data will be read into
 * \return the file represented as a tree, or NULL on failure
 */
bool tree_urlfile_load(const char *filename, struct tree *tree,
		       tree_node_user_callback callback, void *callback_data)
{
	dom_document *document;
	dom_node *html, *body, *ul;
	struct node *root;
	nserror error;
	tree_url_load_ctx ctx;

	if (filename == NULL) {
		return false;
	}

	error = libdom_parse_file(filename, "iso-8859-1", &document);
	if (error != NSERROR_OK) {
		if (error != NSERROR_NOT_FOUND) {
			warn_user("TreeLoadError", messages_get("ParsingFail"));
		}
		return false;
	}

	html = libdom_find_first_element((dom_node *) document,
			corestring_lwc_html);
	if (html == NULL) {
		dom_node_unref(document);
		warn_user("TreeLoadError", "(<html> not found)");
		return false;
	}

	body = libdom_find_first_element(html, corestring_lwc_body);
	if (body == NULL) {
		dom_node_unref(html);
		dom_node_unref(document);
		warn_user("TreeLoadError", "(<html>...<body> not found)");
		return false;
	}

	ul = libdom_find_first_element(body, corestring_lwc_ul);
	if (ul == NULL) {
		dom_node_unref(body);
		dom_node_unref(html);
		dom_node_unref(document);
		warn_user("TreeLoadError",
			  "(<html>...<body>...<ul> not found.)");
		return false;
	}

	root = tree_get_root(tree);

	ctx.tree = tree;
	ctx.directory = root;
	ctx.callback = callback;
	ctx.callback_data = callback_data;
	ctx.last_was_h4 = false;
	ctx.title = NULL;

	tree_url_load_directory(ul, &ctx);
	tree_set_node_expanded(tree, root, true, false, false);

	if (ctx.title != NULL) {
		dom_string_unref(ctx.title);
		ctx.title = NULL;
	}

	dom_node_unref(ul);
	dom_node_unref(body);
	dom_node_unref(html);
	dom_node_unref(document);

	return true;
}

/**
 * Add an entry to the HTML tree for saving.
 *
 * The node must contain a sequence of node_elements in the following order:
 *
 * \param  entry  hotlist entry to add
 * \param  fp     File to write to
 * \return  true on success, false on memory exhaustion
 */
static bool tree_url_save_entry(struct node *entry, FILE *fp)
{
	const char *href, *text;
	char *latin1_href, *latin1_text;
	utf8_convert_ret ret;

	text = tree_url_node_get_title(entry);
	if (text == NULL)
		return false;

	href = tree_url_node_get_url(entry);
	if (href == NULL)
		return false;

	ret = utf8_to_html(text, "iso-8859-1", strlen(text), &latin1_text);
	if (ret != UTF8_CONVERT_OK)
		return false;

	ret = utf8_to_html(href, "iso-8859-1", strlen(href), &latin1_href);
	if (ret != UTF8_CONVERT_OK) {
		free(latin1_text);
		return false;
	}

	fprintf(fp, "<li><a href=\"%s\">%s</a></li>",
			latin1_href, latin1_text);

	free(latin1_href);
	free(latin1_text);

	return true;
}

/**
 * Add a directory to the HTML tree for saving.
 *
 * \param  directory  hotlist directory to add
 * \param  fp         File to write to
 * \return  true on success, false on memory exhaustion
 */
static bool tree_url_save_directory(struct node *directory, FILE *fp)
{
	struct node *child;

	fputs("<ul", fp);
	if (tree_node_is_default(directory))
		fputs(" id=\"default\"", fp);
	fputc('>', fp);

	if (tree_node_get_child(directory) != NULL)
		fputc('\n', fp);

	for (child = tree_node_get_child(directory); child != NULL;
	     child = tree_node_get_next(child)) {
		if (tree_node_is_folder(child) == false) {
			/* entry */
			if (tree_url_save_entry(child, fp) == false)
				return false;
		} else {
			/* directory */
			/* invalid HTML */
			const char *text;
			char *latin1_text;
			utf8_convert_ret ret;

			text = tree_url_node_get_title(child);
			if (text == NULL)
				return false;

			ret = utf8_to_html(text, "iso-8859-1",
					strlen(text), &latin1_text);
			if (ret != UTF8_CONVERT_OK)
				return false;

			fprintf(fp, "<h4>%s</h4>\n", latin1_text);

			free(latin1_text);

			if (tree_url_save_directory(child, fp) == false)
				return false;
		}

		fputc('\n', fp);
	}

	fputs("</ul>", fp);

	return true;
}


/**
 * Perform a save to a specified file in the form of a html page
 *
 * \param filename	the file to save to
 * \param page_title 	title of the page
 */
bool tree_urlfile_save(struct tree *tree, const char *filename,
		       const char *page_title)
{
	FILE *fp;

	fp = fopen(filename, "w");
	if (fp == NULL)
		return NULL;

	/* Unfortunately the Browse Hotlist format is invalid HTML,
	 * so this is a lie. 
	 */
	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", page_title);
	fputs("</head>\n<body>", fp);

	if (tree_url_save_directory(tree_get_root(tree), fp) == false) {
		warn_user("HotlistSaveError", 0);
		fclose(fp);
 		return false;
 	}

	fputs("</body>\n</html>\n", fp);

	fclose(fp);

	return true;
}

