/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  This program 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; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  $Id: kz-bookmark.c 1236 2004-07-18 09:10:14Z ikezoe $
 */

#include "kz-bookmark.h"

#include <string.h>
#include <gtk/gtk.h>
#include "gobject-utils.h"
#include "intl.h"
#include "kz-marshalers.h"
#include "utils.h"
#include "eggregex.h"

#include "kz-io.h"

/* bookmark file types */
#include "kz-xbel.h"
#include "kz-rss.h"
#include "kz-nsbookmark.h"
#include "kz-lirs.h"
#include "kz-hinadi.h"
#include "kz-scrap-bookmark.h"

enum {
	INSERT_CHILD_SIGNAL,
	REMOVE_CHILD_SIGNAL,
	MOVE_CHILD_SIGNAL,
	CHILDREN_REORDERED_SIGNAL,
	LOAD_START_SIGNAL,
	LOAD_COMPLETED_SIGNAL,
	SAVE_START_SIGNAL,
	SAVE_COMPLETED_SIGNAL,
	ERROR_SIGNAL,
	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_TYPE,
	PROP_TITLE,
	PROP_DOC_TITLE,
	PROP_LINK,
	PROP_BOOKMARK_LOCATION,
	PROP_FILE_TYPE,
	PROP_DESCRIPTION,
	PROP_LAST_MODIFIED,
	PROP_INTERVAL,
	PROP_SMART_LIST,
	PROP_EDITABLE
};


#define ADD_BOOKMARK_FILE_TYPE(func)					\
{									\
	KzBookmarkFileType *type;					\
	gint i;								\
	for (i = 0; (type = func(i)); i++)				\
		file_types = g_list_append(file_types, type);		\
}

static void kz_bookmark_class_init   (KzBookmarkClass *klass);
static void kz_bookmark_init         (KzBookmark      *bookmark);
static void kz_bookmark_dispose      (GObject         *object);
static void kz_bookmark_set_property (GObject         *object,
				      guint            prop_id,
				      const GValue    *value,
				      GParamSpec      *pspec);
static void kz_bookmark_get_property (GObject         *object,
				      guint            prop_id,
				      GValue          *value,
				      GParamSpec      *pspec);

static void kz_bookmark_real_insert_child (KzBookmark *bookmark,
					   KzBookmark *child,
					   KzBookmark *sibling);
static void kz_bookmark_real_remove_child (KzBookmark *bookmark,
					   KzBookmark *child);

static void kz_bookmark_set_state         (KzBookmark *bookmark,
					   KzBookmarkState state);
static void kz_bookmark_set_editable      (KzBookmark *bookmark,
					   gboolean    editable);

static gboolean kz_bookmark_is_loading_all_children (KzBookmark *bookmark);

static void     cb_io_load_complete           (KzIO *io,
					       GError *error,
					       KzBookmark   *bookmark);
static gboolean idle_load_complete            (gpointer data);

static void     free_smart_list               (GList *list);

static KzBookmarkFileType *
            kz_bookmark_detect_file_type  (KzBookmark   *bookmark,
					   gchar        *buf);
static GObjectClass *parent_class = NULL;
static gint kz_bookmark_signals[LAST_SIGNAL] = {0};

static GList *file_types = NULL;

static GQuark title_quark             = 0;
static GQuark doc_title_quark         = 0;
static GQuark link_quark              = 0;
static GQuark location_quark          = 0;
static GQuark file_type_quark         = 0;
static GQuark description_quark       = 0;
static GQuark last_mod_quark          = 0;
static GQuark smart_list_quark        = 0;
static GQuark parent_quark            = 0;
static GQuark children_quark          = 0;
static GQuark interval_quark          = 0;
static GQuark timer_quark             = 0;
static GQuark state_quark             = 0;
static GQuark io_quark                = 0;
static GQuark io_signal_id_quark      = 0;


GType
kz_bookmark_type_get_type (void)
{
	static GType etype = 0;
	if (etype == 0)
	{
		static const GEnumValue values[] = {
			{ KZ_BOOKMARK_NORMAL,      "KZ_BOOKMARK_NORMAL",      "normal" },
			{ KZ_BOOKMARK_FOLDER,      "KZ_BOOKMARK_FOLDER",      "folder" },
			{ KZ_BOOKMARK_PURE_FOLDER, "KZ_BOOKMARK_PURE_FOLDER", "pure-folder" },
			{ KZ_BOOKMARK_SEPARATOR,   "KZ_BOOKMARK_SEPARATOR",   "separator" },
			{ 0, NULL, NULL }
		};
		etype = g_enum_register_static ("KzBookmarkType", values);
	}
	return etype;
}


KZ_OBJECT_GET_TYPE(kz_bookmark, "KzBookmark", KzBookmark,
		   kz_bookmark_class_init, kz_bookmark_init,
		   G_TYPE_OBJECT)


static void
kz_bookmark_class_init (KzBookmarkClass *klass)
{
	GObjectClass *object_class;

	if (!file_types)
	{
		ADD_BOOKMARK_FILE_TYPE(kz_rss_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_lirs_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_hinadi_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_xbel_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_nsbookmark_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_scrap_bookmark_get_file_types);
	}

	parent_class = g_type_class_peek_parent (klass);
	object_class = (GObjectClass *) klass;

	object_class->dispose      = kz_bookmark_dispose;
	object_class->set_property = kz_bookmark_set_property;
	object_class->get_property = kz_bookmark_get_property;

	klass->insert_child       = kz_bookmark_real_insert_child;
	klass->remove_child       = kz_bookmark_real_remove_child;
	klass->move_child         = NULL; /* kz_bookmark_real_move_child; */
	klass->children_reordered = NULL;
	klass->load_start         = NULL;
	klass->load_completed     = NULL;
	klass->save_start         = NULL;
	klass->save_completed     = NULL;

	g_object_class_install_property(
		object_class,
		PROP_TYPE,
		g_param_spec_enum (
			"type",
			_("Type"),
			_("Type of the bookmark"),
			KZ_TYPE_BOOKMARK_TYPE,
			KZ_BOOKMARK_NORMAL,
			G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_TITLE,
		 g_param_spec_string(
			 "title",
			 _("Title"),
			 _("The title of the bookmark"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_DOC_TITLE,
		 g_param_spec_string(
			 "document-title",
			 _("Original document title"),
			 _("The original document title of the link"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_LINK,
		 g_param_spec_string(
			 "link",
			 _("Link"),
			 _("The URI of the link"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_BOOKMARK_LOCATION,
		 g_param_spec_string(
			 "location",
			 _("Location of bookmark file itself"),
			 _("The location of the bookmark file itself"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_FILE_TYPE,
		 g_param_spec_string(
			 "file-type",
			 _("File type"),
			 _("File type of the bookmark file"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_DESCRIPTION,
		 g_param_spec_string(
			 "description",
			 _("Description"),
			 _("The description of the bookmark"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_LAST_MODIFIED,
		 g_param_spec_uint(
			 "last-modified",
			 _("Last Modified"),
			 _("Last modification time of the link"),
			 0,
			 G_MAXUINT,
			 0,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_INTERVAL,
		 g_param_spec_uint(
			 "interval",
			 _("Interval"),
			 _("Update interval"),
			 0,
			 G_MAXUINT,
			 0,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_EDITABLE,
		 g_param_spec_boolean(
			 "editable",
			 _("Editable"),
			 _("Whether attributes of the bookmark is writable or not"),
			 TRUE,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_SMART_LIST,
		 g_param_spec_pointer(
			 "smart-list",
			 _("Smart List"),
			 _("The List for the Smart Bookmark"),
			 G_PARAM_READWRITE));

	kz_bookmark_signals[INSERT_CHILD_SIGNAL]
		= g_signal_new ("insert-child",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzBookmarkClass, insert_child),
				NULL, NULL,
				_kz_marshal_VOID__OBJECT_OBJECT,
				G_TYPE_NONE, 2,
				KZ_TYPE_BOOKMARK, KZ_TYPE_BOOKMARK);

	kz_bookmark_signals[REMOVE_CHILD_SIGNAL]
		= g_signal_new ("remove-child",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzBookmarkClass, remove_child),
				NULL, NULL,
				g_cclosure_marshal_VOID__OBJECT,
				G_TYPE_NONE, 1,
				KZ_TYPE_BOOKMARK);

	kz_bookmark_signals[MOVE_CHILD_SIGNAL]
		= g_signal_new ("move-child",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzBookmarkClass, move_child),
				NULL, NULL,
				_kz_marshal_VOID__OBJECT_OBJECT,
				G_TYPE_NONE, 2,
				KZ_TYPE_BOOKMARK, KZ_TYPE_BOOKMARK);

	kz_bookmark_signals[CHILDREN_REORDERED_SIGNAL]
		= g_signal_new ("children-reordered",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzBookmarkClass,
						 children_reordered),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);

	kz_bookmark_signals[LOAD_START_SIGNAL]
		= g_signal_new("load_start",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						load_start),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[LOAD_COMPLETED_SIGNAL]
		= g_signal_new("load_completed",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						load_completed),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[SAVE_START_SIGNAL]
		= g_signal_new("save_start",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						save_start),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[SAVE_COMPLETED_SIGNAL]
		= g_signal_new("save_completed",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						save_completed),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[ERROR_SIGNAL]
		= g_signal_new("error",
			       G_TYPE_FROM_CLASS(klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						error),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__STRING,
			       G_TYPE_NONE, 1, G_TYPE_STRING);

	/* FIXME! add other properties */
	/*
	 * candidates:
	 * 
	 *   * ID
	 *   * Last-Accessed time
	 *   * frequency
	 *   * ......
	 */

	title_quark       = g_quark_from_string("KzBookmark::Title");
	doc_title_quark   = g_quark_from_string("KzBookmark::DocumentTitle");
	link_quark        = g_quark_from_string("KzBookmark::Link");
	location_quark    = g_quark_from_string("KzBookmark::BookmarkLocation");
	file_type_quark   = g_quark_from_string("KzBookmark::FileType");
	description_quark = g_quark_from_string("KzBookmark::Description");
	last_mod_quark    = g_quark_from_string("KzBookmark::LastModified");
	parent_quark      = g_quark_from_string("KzBookmark::Parent");
	children_quark    = g_quark_from_string("KzBookmark::Children");
	interval_quark    = g_quark_from_string("KzBookmark::Interval");
	timer_quark       = g_quark_from_string("KzBookmark::Timer");
	state_quark       = g_quark_from_string("KzBookmark::State");
	io_quark          = g_quark_from_string("KzBookmark::KzIO");
	io_signal_id_quark= g_quark_from_string("KzBookmark::KzIOSignalID");
	smart_list_quark  = g_quark_from_string("KzBookmark::SmartBookmarkList");
}


static void
kz_bookmark_init (KzBookmark *bookmark)
{
	bookmark->type  = KZ_BOOKMARK_NORMAL;
	bookmark->flags = KZ_BOOKMARK_EDITABLE_FLAG;
}


static void
kz_bookmark_dispose (GObject *object)
{
	KzBookmark *bookmark;
	guint timer_id, io_id;
	KzIO *io;
	GList *smart_list;

	g_return_if_fail(KZ_IS_BOOKMARK(object));

	bookmark = KZ_BOOKMARK(object);

	kz_bookmark_remove_all(bookmark);

	timer_id = GPOINTER_TO_UINT(g_object_get_qdata(object, timer_quark));
	if (timer_id)
		g_source_remove(timer_id);
	timer_id = 0;
	g_object_set_qdata(object, timer_quark, GUINT_TO_POINTER(timer_id));

	/* dispose KzIO object if exists */
	/* FIXME! we need more robust way */
	io = g_object_get_qdata(object, io_quark);
	if (io)
	{
		kz_io_stop(io);

	}

	io_id = GPOINTER_TO_UINT(g_object_get_qdata(object, io_signal_id_quark));
	if (io_id)
	{
		/* wait until disconnecting (i.e. loading stops  )*/
		while (g_signal_handler_is_connected(G_OBJECT(io), io_id))
			gtk_main_iteration();
	}
	g_object_set_qdata(object, io_quark, NULL);

	io_id = 0;
	g_object_set_qdata(object, io_signal_id_quark,
			   GUINT_TO_POINTER(io_id));

	/* smart property */
	
	smart_list = g_object_get_qdata(object, smart_list_quark);
	free_smart_list(smart_list);
	g_object_set_qdata(object, smart_list_quark, NULL);
	
	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose(object);
}


#define CHANGE_STR(obj, quark, value) \
{ \
	g_object_set_qdata_full((obj), (quark), (value), \
				(GDestroyNotify) g_free); \
}


static void
free_smart_list (GList *list)
{
	GList *node;

	if (!list) return;

	for (node = list; node; node = g_list_next(node))
	{
		KzBookmarkSmartProperty *prop = node->data;
		if (!prop) continue;
	
		if (prop->regex)
			g_free(prop->regex);
		if (prop->uri)
			g_free(prop->uri);
		if (prop->encode)
			g_free(prop->encode);
		g_free(prop);
	}
	g_list_free(list);
	list = NULL;
}

static void
kz_bookmark_set_property (GObject *object,
			  guint prop_id,
			  const GValue *value,
			  GParamSpec *pspec)
{
	KzBookmark *bookmark = KZ_BOOKMARK(object);

	switch (prop_id) {
	case PROP_TYPE:
		bookmark->type = g_value_get_enum(value);
		break;
	case PROP_TITLE:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, title_quark, g_value_dup_string(value));
		break;
	case PROP_DOC_TITLE:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, doc_title_quark, g_value_dup_string(value));
		break;
	case PROP_LINK:
		g_return_if_fail(!kz_bookmark_is_pure_folder(bookmark));
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, link_quark, g_value_dup_string(value));
		break;
	case PROP_BOOKMARK_LOCATION:
	{
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, location_quark,
			   g_value_dup_string(value));
		break;
	}
	case PROP_FILE_TYPE:
	{
		gchar *str = g_value_dup_string(value);
		KzBookmarkFileType *type;

		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		g_return_if_fail(!kz_bookmark_get_file_type(bookmark));

		CHANGE_STR(object, file_type_quark, str);

		if (str && *str)
		{
			type = kz_bookmark_detect_file_type(bookmark, NULL);
			if (type && type->init)
				type->init(bookmark);
			if (type && !type->to_string)
			{
				kz_bookmark_set_editable(bookmark, FALSE);
				/*
				 * FIXME!!
				 * Also set all children as non-editable.
				 */
			}
		}
		break;
	}
	case PROP_DESCRIPTION:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, description_quark,
			   g_value_dup_string(value));
		break;
	case PROP_LAST_MODIFIED:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		g_object_set_qdata(object, last_mod_quark,
				   GUINT_TO_POINTER(g_value_get_uint(value)));
		break;
	case PROP_INTERVAL:
	{
		guint interval, timer_id;

		interval = g_value_get_uint(value);
		timer_id = GPOINTER_TO_UINT
				(g_object_get_qdata(object, timer_quark));

		if (timer_id)
			g_source_remove(timer_id);
		timer_id = 0;

		if (interval > 0)
			timer_id = g_timeout_add(60000 * interval,
						 (gpointer) kz_bookmark_load_start,
						 bookmark);

		g_object_set_qdata(object, interval_quark,
				   GUINT_TO_POINTER(interval));
		g_object_set_qdata(object, timer_quark,
				   GUINT_TO_POINTER(timer_id));
		break;
	}
	case PROP_EDITABLE:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		if (g_value_get_boolean(value))
			bookmark->flags |= KZ_BOOKMARK_EDITABLE_FLAG;
		else
			bookmark->flags &= ~KZ_BOOKMARK_EDITABLE_FLAG;
		break;
	case PROP_SMART_LIST:
		g_return_if_fail(kz_bookmark_is_smart(bookmark));
		g_object_set_qdata(object, smart_list_quark, 
				   g_value_get_pointer(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
kz_bookmark_get_property (GObject *object,
		     guint prop_id,
		     GValue *value,
		     GParamSpec *pspec)
{
	KzBookmark *bookmark = KZ_BOOKMARK(object);
	gchar *str;
	guint time;

	switch (prop_id) {
	case PROP_TYPE:
		g_value_set_enum(value, bookmark->type);
		break;
	case PROP_TITLE:
		str = g_object_get_qdata(object, title_quark);
		g_value_set_string(value, str);
		break;
	case PROP_DOC_TITLE:
		str = g_object_get_qdata(object, doc_title_quark);
		g_value_set_string(value, str);
		break;
	case PROP_LINK:
		str = g_object_get_qdata(object, link_quark);
		g_value_set_string(value, str);
		break;
	case PROP_BOOKMARK_LOCATION:
		str = g_object_get_qdata(object, location_quark);
		g_value_set_string(value, str);
		break;
	case PROP_FILE_TYPE:
		str = g_object_get_qdata(object, file_type_quark);
		g_value_set_string(value, str);
		break;
	case PROP_DESCRIPTION:
		str = g_object_get_qdata(object, description_quark);
		g_value_set_string(value, str);
		break;
	case PROP_LAST_MODIFIED:
		time = GPOINTER_TO_UINT(g_object_get_qdata(object,
							   last_mod_quark));
		g_value_set_uint(value, time);
		break;
	case PROP_INTERVAL:
	{
		guint interval;

		interval = GPOINTER_TO_UINT
				(g_object_get_qdata(object, interval_quark));
		g_value_set_uint(value, interval);
		break;
	}
	case PROP_EDITABLE:
		if (bookmark->flags & KZ_BOOKMARK_EDITABLE_FLAG)
			g_value_set_boolean(value, TRUE);
		else
			g_value_set_boolean(value, FALSE);
		break;
	case PROP_SMART_LIST:
		g_value_set_pointer(value, 
				    g_object_get_qdata(object, smart_list_quark));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzBookmark *
kz_bookmark_new (void)
{
	KzBookmark *bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
							NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_pure_folder_new (void)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "type", KZ_BOOKMARK_PURE_FOLDER,
					    NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_separator_new (void)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "title", "-----",
					    "type",  KZ_BOOKMARK_SEPARATOR,
					    NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_new_with_attrs (const gchar *title,
			    const gchar *uri,
			    const gchar *description)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "title",       title,
					    "link",        uri,
					    "description", description,
					    NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_file_new (const gchar *location,
		      const gchar *title,
		      const gchar *file_type)
{
	KzBookmark *bookmark;

	location = location ? location : "";

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "type",      KZ_BOOKMARK_FOLDER,
					    "location",  location,
					    "title",     title,
					    "file-type", file_type,
					    NULL));
	bookmark->flags |= KZ_BOOKMARK_FILE_FLAG;
	/* kz_bookmark_load_start(bookmark); */

	return bookmark;
}


KzBookmark *
kz_bookmark_file_create_new (const gchar *location,
			     const gchar *title,
			     const gchar *file_type)
{
	KzBookmark *bookmark;
	KzBookmarkFileType *type;

	if (!location)
	{
		g_warning(_("kz_bookmark_file_create_new(): "
			    "location is not specified!"));
		location = "";
	}

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "type",      KZ_BOOKMARK_FOLDER,
					    "location",  location,
					    "title",     title,
					    "file-type", file_type,
					    NULL));
	bookmark->flags |= KZ_BOOKMARK_FILE_FLAG;

	type = kz_bookmark_detect_file_type(bookmark, NULL);
	if (type && type->from_string)
	{
		if (!kz_bookmark_get_file_type(bookmark))
			kz_bookmark_set_file_type(bookmark, type->file_type);
		type->from_string(bookmark, NULL, 0, NULL);
	}

	return bookmark;
}


const gchar *
kz_bookmark_get_title (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), title_quark);
}


const gchar *
kz_bookmark_get_document_title (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), doc_title_quark);
}


const gchar *
kz_bookmark_get_link (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), link_quark);
}


const gchar *
kz_bookmark_get_description (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), description_quark);
}


const gchar *
kz_bookmark_get_location (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), location_quark);
}


const gchar *
kz_bookmark_get_file_type (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), file_type_quark);
}


guint
kz_bookmark_get_last_modified (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), 0);
	return GPOINTER_TO_UINT(g_object_get_qdata(G_OBJECT(bookmark),
						   last_mod_quark));
}


guint
kz_bookmark_get_interval (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), 0);
	return GPOINTER_TO_UINT(g_object_get_qdata(G_OBJECT(bookmark),
						   interval_quark));
}


gboolean
kz_bookmark_is_editable (KzBookmark *bookmark)
{
	KzBookmark *parent_file;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	parent_file = kz_bookmark_get_parent_file(bookmark);

	if (!KZ_IS_BOOKMARK(parent_file))
		return FALSE;

	if (!kz_bookmark_file_is_editable(parent_file))
		return FALSE;

	return TRUE;
}


gboolean
kz_bookmark_file_is_editable (KzBookmark *bookmark_file)
{
	KzBookmarkFileType *type;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark_file), FALSE);
	g_return_val_if_fail(kz_bookmark_is_file(bookmark_file), FALSE);

	if (!kz_bookmark_get_file_type(bookmark_file))
		return FALSE;

	type = kz_bookmark_detect_file_type(bookmark_file, NULL);
	if (type && type->to_string)
		return TRUE;

	return FALSE;
}


gboolean
kz_bookmark_is_file (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);
	return bookmark->flags & KZ_BOOKMARK_FILE_FLAG ? TRUE : FALSE;
}


void
kz_bookmark_set_title (KzBookmark *bookmark,
		       const gchar *title)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "title", title, NULL);
}


void
kz_bookmark_set_document_title (KzBookmark *bookmark,
				const gchar *doc_title)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "document-title", doc_title, NULL);
}


void
kz_bookmark_set_link (KzBookmark *bookmark,
		      const gchar *uri)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "link", uri, NULL);
}


void
kz_bookmark_set_description (KzBookmark *bookmark,
			     const gchar *description)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "description", description, NULL);
}


void
kz_bookmark_set_location (KzBookmark *bookmark,
			  const gchar *location)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "location", location, NULL);
}


void
kz_bookmark_set_file_type (KzBookmark *bookmark,
			   const gchar *file_type)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "file-type", file_type, NULL);
}


void
kz_bookmark_set_last_modified (KzBookmark *bookmark,
			       guint time)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "last-modified", time, NULL);
}


void
kz_bookmark_set_interval (KzBookmark *bookmark, guint interval)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "interval", interval, NULL);
}


gboolean
kz_bookmark_is_separator (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (bookmark->type == KZ_BOOKMARK_SEPARATOR)
		return TRUE;
	else
		return FALSE;
}


static void
kz_bookmark_set_editable (KzBookmark *bookmark, gboolean editable)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "editable", editable, NULL);
}


gboolean
kz_bookmark_is_folder(KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (bookmark->type == KZ_BOOKMARK_FOLDER ||
	    bookmark->type == KZ_BOOKMARK_PURE_FOLDER)
	{
		return TRUE;
	}

	return FALSE;
}


gboolean
kz_bookmark_is_pure_folder (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (bookmark->type == KZ_BOOKMARK_PURE_FOLDER)
		return TRUE;
	else
		return FALSE;
}


gboolean
kz_bookmark_get_folded (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (!kz_bookmark_is_folder(bookmark)) return TRUE;

	return bookmark->flags & KZ_BOOKMARK_FOLDED_FLAG;
}


void
kz_bookmark_set_folded (KzBookmark  *bookmark, gboolean folded)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (folded)
	{
		bookmark->flags |= KZ_BOOKMARK_FOLDED_FLAG;
	}
	else
	{
		bookmark->flags &= ~KZ_BOOKMARK_FOLDED_FLAG;
	}
}


gboolean
kz_bookmark_is_smart (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);
	return bookmark->flags & KZ_BOOKMARK_SMART_FLAG ? TRUE : FALSE;
}

void
kz_bookmark_append_smart_property (KzBookmark *bookmark,
				   const gchar *regex,
				   const gchar *uri,
				   const gchar *encode,
				   gboolean urlencode)
{
	GList *smart_list;
	KzBookmarkSmartProperty *prop;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	bookmark->flags |= KZ_BOOKMARK_SMART_FLAG;

	prop = g_new0(KzBookmarkSmartProperty, 1);
	if (regex)
		prop->regex = g_strdup(regex);
	if (uri)
		prop->uri   = g_strdup(uri);
	if (encode)
		prop->encode = g_strdup(encode);
	prop->urlencode = urlencode;

	smart_list = g_object_get_qdata(G_OBJECT(bookmark),
					smart_list_quark);
	smart_list = g_list_append(smart_list, prop);

	g_object_set(G_OBJECT(bookmark), "smart-list", smart_list, NULL);
}

void
kz_bookmark_remove_smart_property (KzBookmark *bookmark)
{
}

#define MAX_GROUPSTRING 16
gchar *
kz_bookmark_get_smart_uri (KzBookmark *bookmark, const gchar *text)
{
	gchar *smart_uri;
	GList *smart_list, *node;
	KzBookmarkSmartProperty *match = NULL;
	EggRegex *egg_regex;
	GError *error = NULL;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	if (!(bookmark->flags & KZ_BOOKMARK_SMART_FLAG))
		return NULL;

	if (!text)
	{
		smart_uri = g_strdup(kz_bookmark_get_link(bookmark));
		return smart_uri;
	}

	smart_list = g_object_get_qdata(G_OBJECT(bookmark),
					smart_list_quark);

	for (node = smart_list; node; node = g_list_next(node))
	{
		gchar *regex;
		KzBookmarkSmartProperty *prop;
		
		prop = node->data;	
		regex = prop->regex;

		egg_regex = egg_regex_new(regex, 0, 0, &error);

		if (error)
		{
			g_warning("KzBookmark: Regular Expression Error");
			g_error_free(error);
			return NULL;
		}

		if (egg_regex_match(egg_regex, text, -1, 0) > 0)
		{
			match = prop;
			break;
		}
		egg_regex_free(egg_regex);
	}

	if(match)
	{
		gboolean urlencode = match->urlencode;
		gchar *uri = match->uri;
		gchar *encode = match->encode;
		gchar **strings = NULL;
		gchar *url_strings[MAX_GROUPSTRING];
		guint i, n = 0;
		GString *str;

		strings = egg_regex_fetch_all(egg_regex, text);
	
		if (strings)
		{
			while (strings[n] && n < MAX_GROUPSTRING)
			{
				GError *e = NULL;
				gchar *encode_string = NULL;

				if (encode)
				{
					encode_string = 
						g_convert(strings[n],
							  strlen(strings[n]),
							  encode, "UTF-8",
							  NULL, NULL,
							  &e);
				}
				
				if (e || !encode)
				{
					encode_string =
					    g_strdup(strings[n]);
					if (e)
						g_error_free(e);
				}
				if (urlencode)	
				{
					url_strings[n] = 
						url_encode(encode_string);
				}
				else
				{
					url_strings[n] = g_strdup(encode_string);
				}

				g_free(encode_string);
				n++;
			}
		}
		g_strfreev(strings);	

		str = g_string_new_len(uri, strlen(uri));
		if (strstr(uri, "%s"))
		{
			gchar *encode_string = NULL;
			gchar *url;
			GError *e = NULL;

			if (encode)
			{
				encode_string = 
					g_convert(text,
						  strlen(text),
						  encode, "UTF-8",
						  NULL, NULL,
						  &e);
			}
				
			if (e || !encode)
			{
				encode_string =
				    g_strdup(text);
				if (e)
					g_error_free(e);
			}
			if (urlencode)	
			{
				url = url_encode(encode_string);
			}
			else
			{
				url = g_strdup(encode_string);
			}
			g_string_printf(str, uri, url);
			g_free(url);
			g_free(encode_string);
		}

		for (i = 0; i < n; i++)
		{
			gchar *pos;
			gchar *index;
			index = g_strdup_printf("\\%d", i);
			pos = strstr(str->str, index); 
			if (pos)
			{
				gssize p = pos - str->str;
				str = g_string_erase(str, p, strlen(index));
				str = g_string_insert(str, p, url_strings[i]); 
			}
			g_free(index);
		}
		
		smart_uri = g_strndup(str->str, str->len);	
		g_string_free(str, TRUE);

		egg_regex_free(egg_regex);
	}
	else
	{
                /* match nothing so nothing to do... */
		smart_uri = g_strdup(text);
	}

	return smart_uri;
}


static void
kz_bookmark_real_insert_child (KzBookmark *bookmark,
			       KzBookmark *child,
			       KzBookmark *sibling)
{
	GList *list, *next;

	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(!sibling || KZ_IS_BOOKMARK(sibling));
	g_return_if_fail(kz_bookmark_is_folder(bookmark));

	list = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	next = g_list_find(list, sibling);

	g_object_ref(child);
	list = g_list_insert_before(list, next, child);
	g_object_set_qdata(G_OBJECT(bookmark), children_quark, list);

	g_object_set_qdata(G_OBJECT(child), parent_quark, bookmark);
}


void
kz_bookmark_insert_before (KzBookmark *bookmark,
			   KzBookmark *child,
			   KzBookmark *sibling)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[INSERT_CHILD_SIGNAL], 0,
		      child, sibling);
}


void
kz_bookmark_append (KzBookmark *bookmark, KzBookmark *child)
{
	kz_bookmark_insert_before(bookmark, child, NULL);
}


void
kz_bookmark_prepend (KzBookmark *bookmark, KzBookmark *child)
{
	GList *list;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	list = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	if (list)
		kz_bookmark_insert_before(bookmark, child, list->data);
	else
		kz_bookmark_insert_before(bookmark, child, NULL);		
}


static void
kz_bookmark_real_remove_child (KzBookmark *bookmark, KzBookmark *child)
{
	GList *list;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(kz_bookmark_is_folder(bookmark));

	list = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	if (!g_list_find(list, child)) return;

	list = g_list_remove(list, child);
	g_object_unref(child);
	g_object_set_qdata(G_OBJECT(bookmark), children_quark, list);

	g_object_set_qdata(G_OBJECT(child), parent_quark, NULL);	
}


void
kz_bookmark_remove (KzBookmark *bookmark, KzBookmark *child)
{
	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[REMOVE_CHILD_SIGNAL], 0,
		      child);
}


void
kz_bookmark_remove_all (KzBookmark *bookmark)
{
	GList *children, *node, *prev;

	children = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	children = g_list_copy(children);
	children = g_list_last(children);

	node = children;
	while (node)
	{
		KzBookmark *child = node->data;
		prev = g_list_previous(node);
		kz_bookmark_remove (bookmark, child);
		node = prev;
	}

	g_list_free(children);
}


KzBookmark *
kz_bookmark_get_parent (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	return g_object_get_qdata(G_OBJECT(bookmark), parent_quark);
}


KzBookmark *
kz_bookmark_get_parent_file (KzBookmark *bookmark)
{
	KzBookmark *parent = bookmark;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	do
	{
		parent = kz_bookmark_get_parent(parent);
		if (KZ_IS_BOOKMARK(parent) && kz_bookmark_is_file(parent))
			return parent;
	} while (parent);

	return NULL;
}


GList *
kz_bookmark_get_children (KzBookmark *bookmark)
{
	GList *list;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	list =  g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	if (list)
		return g_list_copy(list);

	return NULL;
}


gboolean
kz_bookmark_has_children (KzBookmark  *bookmark)
{
	GList *list;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	list =  g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	if (list)
		return TRUE;
	else
		return FALSE;
}


KzBookmark *
kz_bookmark_next (KzBookmark *bookmark)
{
	KzBookmark *parent;
	GList *list, *node;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	parent = kz_bookmark_get_parent(bookmark);
	if (!parent) return NULL;

	list = g_object_get_qdata(G_OBJECT(parent), children_quark);
	node = g_list_find(list, bookmark);
	if (node && (node = g_list_next(node)))
		return node->data;

	return NULL;
}


KzBookmark *
kz_bookmark_prev (KzBookmark  *bookmark)
{
	KzBookmark *parent;
	GList *list, *node;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	parent = kz_bookmark_get_parent(bookmark);
	if (!parent) return NULL;

	list = g_object_get_qdata(G_OBJECT(parent), children_quark);
	node = g_list_find(list, bookmark);
	if (node && (node = g_list_previous(node)))
		return node->data;

	return NULL;
}


static gint 
compare_func (gconstpointer a, gconstpointer b)
{
	guint one = kz_bookmark_get_last_modified((KzBookmark*)a);
	guint two = kz_bookmark_get_last_modified((KzBookmark*)b);
	return two - one;
}


void
kz_bookmark_sort (KzBookmark *bookmark, const gchar *type)
{
	GList *children;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(kz_bookmark_is_folder(bookmark));

	if (!type) return;

	children = g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	/* FIXME! use hash table */
	if (!strcmp(type, KZ_BOOKMARK_SORT_LAST_MODIFIED))
		children = g_list_sort(children, compare_func);
	else
		return;

	g_object_set_qdata(G_OBJECT(bookmark), children_quark, children);

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[CHILDREN_REORDERED_SIGNAL], 0);
}


void
kz_bookmark_regist_sort_func (const gchar *type, GCompareFunc *func)
{
	g_warning("kz_bookmark_regist_sort_func() is not implemented yet.");
}



/*****************************************************************************
 *                                                                           *
 *                        load/save booomark file                            *
 *                                                                           *
 *****************************************************************************/
gboolean
kz_bookmark_load_start (KzBookmark *bookmark)
{
	KzIO *io;
	const gchar *uri;
	guint id;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	uri = kz_bookmark_get_location(KZ_BOOKMARK(bookmark));
	if (!uri) return FALSE;

	/* check loading state */
	if (kz_bookmark_is_loading_all_children(bookmark))
		return TRUE;
	kz_bookmark_set_state(bookmark, KZ_BOOKMARK_STATE_LOADING);

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[LOAD_START_SIGNAL],
		      0);

	kz_bookmark_remove_all(KZ_BOOKMARK(bookmark));

	io = kz_io_new(uri);
	id = g_signal_connect(G_OBJECT(io), "io_completed",
			      G_CALLBACK(cb_io_load_complete),
			      bookmark);
	
	g_object_set_qdata(G_OBJECT(bookmark), io_quark, io);
	g_object_set_qdata(G_OBJECT(bookmark), io_signal_id_quark,
			   GUINT_TO_POINTER(id));

	kz_io_load_to_buffer(io);

	return TRUE;
}


void
kz_bookmark_load_stop (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_load_stop() is not implemented yet.");
}


gboolean
kz_bookmark_save_start (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_save_start() is not implemented yet.");
	return FALSE;
}


void
kz_bookmark_save_stop (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_save_stop() is not implemented yet.");
}


void
kz_bookmark_load (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_load() is not implemented yet.");
}


void
kz_bookmark_save (KzBookmark *bookmark)
{
	KzBookmarkFileType *type;
	KzIO *io;
	const gchar *file;
	gchar *str;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	type = kz_bookmark_detect_file_type(bookmark, NULL);
	if (!type || !type->to_string) return;

	file = kz_bookmark_get_location(bookmark);
	str = type->to_string(bookmark);

	if (file && *file && str && *str)
	{
		io = kz_io_new(file);
		kz_io_write(io, str);
		g_object_unref(io);
		g_free(str);
	}
}


static gboolean
idle_load_complete(gpointer data)
{
	KzIO *io = data;

	g_object_unref(G_OBJECT(io));

	return FALSE;
}


static void
cb_io_load_complete(KzIO *io, GError *error, KzBookmark *bookmark)
{	
	KzBookmarkFileType *type;
	const gchar *buf;
	guint size;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(KZ_IS_IO(io));

	if (error) goto ERROR;

	buf  = kz_io_get_buffer(io);
	size = kz_io_get_loaded_size(io);

	g_return_if_fail(buf != NULL);
	if (size == 0) goto ERROR;

	type = kz_bookmark_detect_file_type(bookmark, (gchar *)buf);
	if (!type)
		/* use default? */;
	if (type && type->from_string)
	{
		if (!kz_bookmark_get_file_type(bookmark))
			kz_bookmark_set_file_type(bookmark, type->file_type);
		type->from_string(bookmark, buf, size, NULL);
	}

ERROR:

	g_signal_handlers_disconnect_by_func
		(G_OBJECT(io),
		 G_CALLBACK(cb_io_load_complete),
		 bookmark);
	kz_bookmark_set_state(bookmark, KZ_BOOKMARK_STATE_NORMAL);
	g_object_set_qdata(G_OBJECT(bookmark), io_quark, NULL);
	g_object_set_qdata(G_OBJECT(bookmark), io_signal_id_quark,
			   GPOINTER_TO_UINT(0));

	if (error)
	{
		g_signal_emit(G_OBJECT (bookmark),
			      kz_bookmark_signals[ERROR_SIGNAL],
			      0,
			      "Load failed");
	}
	else 
	{
		g_signal_emit(G_OBJECT (bookmark),
			      kz_bookmark_signals[LOAD_COMPLETED_SIGNAL],
			      0);
	}
	g_idle_add(idle_load_complete, io);
}


static KzBookmarkFileType *
kz_bookmark_detect_file_type(KzBookmark *bookmark, gchar *buf)
{
	GList *node;
	const gchar *type_str;

	type_str = kz_bookmark_get_file_type(bookmark);
	g_return_val_if_fail(type_str || buf, NULL);

	for (node = file_types; node; node = g_list_next(node))
	{
		KzBookmarkFileType *type = node->data;
 
		if (type_str)
		{
			/* Fixed, or already detected */

			if (type->file_type &&
			    !strcmp(type_str, type->file_type))
			{
				return type;
			}
		}
		else
		{
			/* Auto detect */

			if (type && type->is_supported &&
			    type->is_supported(bookmark, buf))
			{
				return type;
			}
		}
	}

	return NULL;
}


static void
kz_bookmark_set_state (KzBookmark *bookmark, KzBookmarkState state)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (state == KZ_BOOKMARK_STATE_LOADING)
		g_object_set_qdata(G_OBJECT(bookmark), state_quark,
				   GINT_TO_POINTER(state));
	else
		g_object_set_qdata(G_OBJECT(bookmark), state_quark,
				   GINT_TO_POINTER(KZ_BOOKMARK_STATE_NORMAL));
}


KzBookmarkState
kz_bookmark_get_state (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark),
			     KZ_BOOKMARK_STATE_NORMAL);

	return GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(bookmark),
						  state_quark));
}


void
kz_bookmark_set_smart_list (KzBookmark *bookmark, GList *list)
{
	GList *old_list;
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	
	if (!kz_bookmark_is_smart(bookmark)) return;
	
	old_list = g_object_get_qdata(G_OBJECT(bookmark),
				      smart_list_quark);
	free_smart_list(old_list);

	g_object_set(G_OBJECT(bookmark), "smart-list", list, NULL);
}


const GList *
kz_bookmark_get_smart_list (KzBookmark *bookmark)
{
	const GList *list;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	
	if (!kz_bookmark_is_smart(bookmark)) return NULL;

	list = g_object_get_qdata(G_OBJECT(bookmark),
				  smart_list_quark);

	return list;
}

static gboolean
kz_bookmark_is_loading_all_children (KzBookmark *bookmark)
{
	gboolean ret = FALSE;
	KzBookmarkState state;
	GList *children, *node, *next;
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);
	
	/* check loading state */
	children = g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	node = children;
	while (node)
	{
		KzBookmark *child = node->data;
		if (kz_bookmark_has_children(child))
		{
			ret = kz_bookmark_is_loading_all_children(child);
			if (ret) break;
		}
		next = g_list_next(node);
		state = kz_bookmark_get_state(child);
		ret = (state == KZ_BOOKMARK_STATE_LOADING) ? TRUE : FALSE;
		if (ret) break;
		node = next;
	}

	return ret;
}


