/***************************************************************************
              downloads-list.c - custom tree model for the downloads
              ------------------------------------------------------
    begin                : Wed Oct 1 2003
    copyright            : (C) 2003 by Tim-Philipp Müller
    email                : t.i.m at orange dot net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "blacklist.h"
#include "core-conn.h"
#include "downloads-list.h"
#include "icons.h"
#include "misc.h"
#include "misc_strings.h"
#include "misc_sys.h"
#include "options.h"

#include "status_page.h"
#include "statusbar.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* how often do we get status packets, in seconds */
#define STATUS_INTERVAL              3

#define GAPSTATUS_ASKED              1000
#define GAPSTATUS_FILLED             2000

#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcellrendererpixbuf.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtklabel.h>
#include <gtk/gtktreesortable.h>
#include <gtk/gtkvbox.h>

/* enums for our own properties */
enum
{
  PROP_HIDE_JUNK = 1,
  PROP_TRANS_IN_PERCENT =2,
};

/* enums for our own signals */
enum
{
  DOWNLOAD_REMOVED = 0,
  LAST_SIGNAL
};

typedef enum
{
  SIGNAL_ROW_INSERTED,
  SIGNAL_ROW_DELETED,
  SIGNAL_ROW_CHANGED,
} GuiDownloadListRowSignal;


static guint        gui_download_list_signals[LAST_SIGNAL]; /* all 0 */



static void         gui_download_list_init            (GuiDownloadList      *pkg_tree);

static void         gui_download_list_class_init      (GuiDownloadListClass *klass);

static void         gui_download_list_tree_model_init (GtkTreeModelIface *iface);

static void         gui_download_list_set_property    (GObject           *object,
                                                       guint              param_id,
                                                       const GValue      *value,
                                                       GParamSpec        *pspec);

static void         gui_download_list_finalize        (GObject           *object);

static GtkTreeModelFlags gui_download_list_get_flags  (GtkTreeModel      *tree_model);

static gint         gui_download_list_get_n_columns   (GtkTreeModel      *tree_model);

static GType        gui_download_list_get_column_type (GtkTreeModel      *tree_model,
                                                       gint               index);

static gboolean     gui_download_list_get_iter        (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter,
                                                       GtkTreePath       *path);

static GtkTreePath *gui_download_list_get_path        (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter);

static void         gui_download_list_get_value       (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter,
                                                       gint               column,
                                                       GValue            *value);

static gboolean     gui_download_list_iter_next       (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter);

static gboolean     gui_download_list_iter_children   (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter,
                                                       GtkTreeIter       *parent);

static gboolean     gui_download_list_iter_has_child  (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter);

static gint         gui_download_list_iter_n_children (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter);

static gboolean     gui_download_list_iter_nth_child  (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter,
                                                       GtkTreeIter       *parent,
                                                       gint               n);

static gboolean     gui_download_list_iter_parent     (GtkTreeModel      *tree_model,
                                                       GtkTreeIter       *iter,
                                                       GtkTreeIter       *child);
/* -- GtkTreeSortable interface functions -- */

static void         gui_download_list_sortable_init                  (GtkTreeSortableIface *iface);

static gboolean     gui_download_list_sortable_get_sort_column_id    (GtkTreeSortable *sortable,
                                                                      gint            *sort_col_id,
                                                                      GtkSortType     *order);

static void         gui_download_list_sortable_set_sort_column_id    (GtkTreeSortable *sortable,
                                                                      gint             sort_col_id,
                                                                      GtkSortType      order);

static void         gui_download_list_sortable_set_sort_func         (GtkTreeSortable        *sortable,
                                                                      gint                    sort_col_id,
                                                                      GtkTreeIterCompareFunc  sort_func,
                                                                      gpointer                user_data,
                                                                      GtkDestroyNotify        destroy_func);

static void         gui_download_list_sortable_set_default_sort_func (GtkTreeSortable        *sortable,
                                                                      GtkTreeIterCompareFunc  sort_func,
                                                                      gpointer                user_data,
                                                                      GtkDestroyNotify        destroy_func);

static gboolean     gui_download_list_sortable_has_default_sort_func (GtkTreeSortable *sortable);

static void         gui_download_list_resort                         (GuiDownloadList *gui_download_list);


/* other functions */

static void         gui_download_list_emit_tree_model_signal         (GuiDownloadListRowSignal      which,
                                                                      GuiDownload                  *item);

static void         gui_download_list_resume_a_paused_file           (GuiDownloadList *list);

static void         gui_download_list_remove_download                (GuiDownloadList *list, GuiDownload *dl);


/***************************************************************************
 *
 *   sil_emit_tree_model_signal
 *
 *   Tell the view that something has changed
 *
 ***************************************************************************/

static void
gui_download_list_emit_tree_model_signal (GuiDownloadListRowSignal which, GuiDownload *item)
{
	GtkTreePath *path;
	GtkTreeIter  iter;

	g_return_if_fail ( item       != NULL );
	g_return_if_fail ( item->list != NULL );

	iter.stamp = (item->list)->stamp;
	iter.user_data = item;

	path = gui_download_list_get_path(GTK_TREE_MODEL(item->list), &iter);

	g_return_if_fail ( path != NULL );

	switch (which)
	{
		case SIGNAL_ROW_INSERTED:
			gtk_tree_model_row_inserted(GTK_TREE_MODEL(item->list), path, &iter);
			break;

		case SIGNAL_ROW_DELETED:

			g_signal_emit (item->list, gui_download_list_signals[DOWNLOAD_REMOVED],
                       0 /* details */,
                       item, NULL);

			gtk_tree_model_row_deleted(GTK_TREE_MODEL(item->list), path);
			break;

		case SIGNAL_ROW_CHANGED:
			gtk_tree_model_row_changed(GTK_TREE_MODEL(item->list), path, &iter);
			break;
	}

	gtk_tree_path_free(path);
}





static GObjectClass *parent_class = NULL;





GType
gui_download_list_get_type (void)
{
	static GType gui_download_list_type = 0;

	if (gui_download_list_type)
	  return gui_download_list_type;

	if (1);
	{
		static const GTypeInfo gui_download_list_info =
		{
			sizeof (GuiDownloadListClass),
			NULL,                                         /* base_init */
			NULL,                                         /* base_finalize */
			(GClassInitFunc) gui_download_list_class_init,
			NULL,                                         /* class finalize */
			NULL,                                         /* class_data */
			sizeof (GuiDownloadList),
			0,                                           /* n_preallocs */
			(GInstanceInitFunc) gui_download_list_init
		};

		gui_download_list_type = g_type_register_static (G_TYPE_OBJECT, "GuiDownloadList",
		                                             &gui_download_list_info, (GTypeFlags)0);
	}

	/* Register GtkTreeModel interface */
	if (1)
	{
		static const GInterfaceInfo tree_model_info =
		{
			(GInterfaceInitFunc) gui_download_list_tree_model_init,
			NULL,
			NULL
		};

		g_type_add_interface_static (gui_download_list_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
	}

	/* Register GtkTreeModel interface */
	if (1)
	{
		static const GInterfaceInfo tree_sortable_info =
		{
			(GInterfaceInitFunc) gui_download_list_sortable_init,
			NULL,
			NULL
		};

		g_type_add_interface_static (gui_download_list_type, GTK_TYPE_TREE_SORTABLE, &tree_sortable_info);
	}

  return gui_download_list_type;
}


static void
gui_download_list_class_init (GuiDownloadListClass *klass)
{
  GObjectClass *object_class;

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

  object_class->finalize = gui_download_list_finalize;

	object_class->set_property = gui_download_list_set_property;

  g_object_class_install_property (object_class,
	                                 PROP_HIDE_JUNK,
	                                 g_param_spec_boolean("hide-junk",
	                                                      "hide-junk",
	                                                      "Whether filenames should be cleaned from junk bits",
	                                                      opt_get_bool(OPT_GUI_DOWNLOADS_HIDE_JUNK),
	                                                      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class,
	                                 PROP_TRANS_IN_PERCENT,
	                                 g_param_spec_boolean("trans-in-percent",
	                                                      "trans-in-percent",
	                                                      "Whether the amount transfered should be shown as a percentage",
	                                                      opt_get_bool(OPT_GUI_DOWNLOADS_SHOW_TRANSFERED_IN_PERCENT),
	                                                      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));

	gui_download_list_signals[DOWNLOAD_REMOVED] =
		g_signal_new("download-removed",
		             G_OBJECT_CLASS_TYPE(object_class),
		             G_SIGNAL_RUN_FIRST,
		             G_STRUCT_OFFSET(GuiDownloadListClass, download_removed),
		             NULL, NULL,
		             g_cclosure_marshal_VOID__POINTER,
		             G_TYPE_NONE,
		             1, G_TYPE_POINTER);

}

static void
gui_download_list_tree_model_init (GtkTreeModelIface *iface)
{
	iface->get_flags       = gui_download_list_get_flags;
	iface->get_n_columns   = gui_download_list_get_n_columns;
	iface->get_column_type = gui_download_list_get_column_type;
	iface->get_iter        = gui_download_list_get_iter;
	iface->get_path        = gui_download_list_get_path;
	iface->get_value       = gui_download_list_get_value;
	iface->iter_next       = gui_download_list_iter_next;
	iface->iter_children   = gui_download_list_iter_children;
	iface->iter_has_child  = gui_download_list_iter_has_child;
	iface->iter_n_children = gui_download_list_iter_n_children;
	iface->iter_nth_child  = gui_download_list_iter_nth_child;
	iface->iter_parent     = gui_download_list_iter_parent;
}

static void
gui_download_list_sortable_init (GtkTreeSortableIface *iface)
{
	iface->get_sort_column_id    = gui_download_list_sortable_get_sort_column_id;
	iface->set_sort_column_id    = gui_download_list_sortable_set_sort_column_id;
	iface->set_sort_func         = gui_download_list_sortable_set_sort_func;          /* NOT SUPPORTED */
	iface->set_default_sort_func = gui_download_list_sortable_set_default_sort_func;  /* NOT SUPPORTED */
	iface->has_default_sort_func = gui_download_list_sortable_has_default_sort_func;  /* NOT SUPPORTED */
}


static void
gui_download_list_init (GuiDownloadList *list)
{
	list->n_columns                                      = GUI_DOWNLOAD_LIST_N_COLUMNS;
	list->column_types[GUI_DOWNLOAD_LIST_DOT]            = GDK_TYPE_PIXBUF;
	list->column_types[GUI_DOWNLOAD_LIST_NAME]           = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_SIZE_STR]       = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_STATUS_STR]     = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_TRANSFERED_STR] = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_LEFT_STR]       = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_SPEED_STR]      = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_TIME_LEFT_STR]  = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_PRIORITY_STR]   = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_GAPLIST]        = G_TYPE_POINTER;
	list->column_types[GUI_DOWNLOAD_LIST_PAUSED_BOOL]    = G_TYPE_BOOLEAN;
	list->column_types[GUI_DOWNLOAD_LIST_SOURCES_STR]    = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_LAST_DL_STR]    = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_LAST_COMPLETE_STR] = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_TYPE_STR]       = G_TYPE_STRING;
	list->column_types[GUI_DOWNLOAD_LIST_FORMAT_STR]     = G_TYPE_STRING;

	g_assert ( GUI_DOWNLOAD_LIST_N_COLUMNS == 16 );

	list->num_rows = 0;
	list->rows      = NULL;

	list->sort_id    = -1; /* no sorting */
	list->sort_order = GTK_SORT_ASCENDING;

	list->flags      = 0;

	list->get_gaps_interval = (opt_get_bool(OPT_GUI_SLOW_GUI_CORE_CONNECTION)) ? 30: 15;

	list->stamp = g_random_int();

	/* create translated status strings */

	list->status_utf8 = g_new0 (gchar *, STATUS_LAST+2);

	list->status_utf8[STATUS_HASHING]      = TO_UTF8(_("(Hashing...)"));
	list->status_utf8[STATUS_QUEUED]       = TO_UTF8(_("Queued."));
	list->status_utf8[STATUS_LOOKING]      = TO_UTF8(_("Looking..."));
	list->status_utf8[STATUS_DOWNLOADING]  = TO_UTF8(_("Downloading..."));
	list->status_utf8[STATUS_PAUSED]       = TO_UTF8(_("Paused."));
	list->status_utf8[STATUS_IDS]          = TO_UTF8(_("Insufficient Disk Space."));
	list->status_utf8[STATUS_NOSRCS]       = TO_UTF8(_("No Sources."));
	list->status_utf8[STATUS_HASHING2]     = TO_UTF8(_("Hashing..."));
	list->status_utf8[STATUS_ERRLOADING]   = TO_UTF8(_("Error Loading."));
	list->status_utf8[STATUS_COMPLETING]   = TO_UTF8(_("Completing..."));
	list->status_utf8[STATUS_COMPLETE]     = TO_UTF8(_("Complete."));
	list->status_utf8[STATUS_CORRUPTED]    = TO_UTF8(_("Complete, but corrupted."));
	list->status_utf8[STATUS_TRANSFERRING] = TO_UTF8(_("Transferring..."));
	list->status_utf8[STATUS_RESUMING]     = TO_UTF8(_("Resuming..."));
	list->status_utf8[STATUS_PAUSING]      = TO_UTF8(_("Pausing..."));
	list->status_utf8[STATUS_CANCELLING]   = TO_UTF8(_("Cancelling..."));
	list->status_utf8[STATUS_LAST]         = TO_UTF8(_("(invalid)"));
}


/******************************************************************************
 *
 *  gui_download_list_new
 *
 ******************************************************************************/

GuiDownloadList *
gui_download_list_new (void)
{
	GuiDownloadList *list;

	list = (GuiDownloadList*) g_object_new (GUI_TYPE_DOWNLOAD_LIST, NULL);

	return list;
}



/******************************************************************************
 *
 *  gui_download_list_set_property
 *
 ******************************************************************************/

static void
gui_download_list_set_property (GObject           *object,
                                guint              param_id,
                                const GValue      *value,
                                GParamSpec        *pspec)
{
	GuiDownloadList *list = GUI_DOWNLOAD_LIST(object);

	switch (param_id)
	{
		case PROP_HIDE_JUNK:
		{
			gboolean oldval = ((list->flags & GUI_DOWNLOAD_LIST_FLAG_HIDE_JUNK) != 0);
			gboolean newval = g_value_get_boolean(value);

			if (oldval != newval)
			{
				GuiDownload **pos;

				if (newval)
					list->flags |= GUI_DOWNLOAD_LIST_FLAG_HIDE_JUNK;
				else
					list->flags &= (~GUI_DOWNLOAD_LIST_FLAG_HIDE_JUNK);

				/* setting all nojunk names to NULL will regenerate them if needed */
				for (pos = list->rows; ((pos) && (*pos)); ++pos)
				{
					G_FREE((*pos)->name_nojunk_utf8);
					G_FREE((*pos)->name_nojunk_collate_key);
					gui_download_list_emit_tree_model_signal(SIGNAL_ROW_CHANGED, *pos);
				}
			}
		}
		break;

		case PROP_TRANS_IN_PERCENT:
		{
			if (g_value_get_boolean(value))
				list->flags |= GUI_DOWNLOAD_LIST_FLAG_TRANS_PERC;
			else
				list->flags &= (~GUI_DOWNLOAD_LIST_FLAG_TRANS_PERC);
		}
		break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(object,param_id, pspec);
			break;
	}
}


/******************************************************************************
 *
 *  gui_download_list_finalize
 *
 ******************************************************************************/

static void
gui_download_list_finalize (GObject *object)
{
	GuiDownloadList *list = GUI_DOWNLOAD_LIST(object);

	/* give back all memory */

  while (list->num_rows > 0)
		gui_download_list_remove_download(list, list->rows[0]);

	g_strfreev(list->status_utf8);

	/* must chain up */
	(* parent_class->finalize) (object);
}

/******************************************************************************
 *
 *  gui_download_list_clear
 *
 ******************************************************************************/

void
gui_download_list_clear (GuiDownloadList *list)
{
	g_return_if_fail (GUI_IS_DOWNLOAD_LIST(list));

  while (list->num_rows > 0)
		gui_download_list_remove_download(list, list->rows[0]);
}


/******************************************************************************
 *
 *  gui_download_list_get_flags
 *
 ******************************************************************************/

static GtkTreeModelFlags
gui_download_list_get_flags (GtkTreeModel *tree_model)
{
	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST(tree_model), (GtkTreeModelFlags)0);

	return ( GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST );
}


/******************************************************************************
 *
 *  gui_download_list_get_n_columns
 *
 ******************************************************************************/

static gint
gui_download_list_get_n_columns (GtkTreeModel *tree_model)
{
	GuiDownloadList *list = (GuiDownloadList *) tree_model;

	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST(tree_model), 0);

	return list->n_columns;
}


/******************************************************************************
 *
 *  gui_download_list_get_column_type
 *
 ******************************************************************************/

static GType
gui_download_list_get_column_type (GtkTreeModel *tree_model,
                                   gint          index)
{
	GuiDownloadList *tree_store = (GuiDownloadList *) tree_model;

	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST(tree_model), G_TYPE_INVALID);
	g_return_val_if_fail (index < GUI_DOWNLOAD_LIST(tree_model)->n_columns && index >= 0, G_TYPE_INVALID);

  return tree_store->column_types[index];
}


/******************************************************************************
 *
 *  gui_download_list_get_iter
 *
 ******************************************************************************/

static gboolean
gui_download_list_get_iter (GtkTreeModel *tree_model,
                            GtkTreeIter  *iter,
                            GtkTreePath  *path)
{
	GuiDownloadList *list = (GuiDownloadList *) tree_model;
	GuiDownload     *dl;
	gint            *indices, depth, rownum;

	g_assert(GUI_IS_DOWNLOAD_LIST(list));
	g_assert(path!=NULL);

	indices = gtk_tree_path_get_indices(path);
	depth   = gtk_tree_path_get_depth(path);

	/* we do not allow children */
	g_assert(depth == 1);

	rownum = indices[0];

	if ( rownum >= list->num_rows )
		return FALSE;

	dl = list->rows[rownum];

	g_assert(dl);
	g_assert(dl->pos == rownum);

	iter->stamp      = list->stamp;
	iter->user_data  = dl;
	iter->user_data2 = NULL;
	iter->user_data3 = NULL;

	return TRUE;
}


/******************************************************************************
 *
 *  gui_download_list_get_path
 *
 ******************************************************************************/

static GtkTreePath *
gui_download_list_get_path (GtkTreeModel *tree_model,
                            GtkTreeIter  *iter)
{
	GuiDownloadList *list = (GuiDownloadList *) tree_model;
	GuiDownload     *dl;
	GtkTreePath     *path;

	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST(tree_model), NULL);
	g_return_val_if_fail (iter != NULL, NULL);
	g_return_val_if_fail (iter->stamp == list->stamp, NULL);
	g_return_val_if_fail (iter->user_data != NULL, NULL);

	path = gtk_tree_path_new();
	dl = (GuiDownload*) iter->user_data;
	gtk_tree_path_append_index(path, dl->pos);

	return path;
}


/******************************************************************************
 *
 *  gui_download_list_value_set_uint_string
 *
 *  Transform the given value into a unsigned int string and
 *   an optional unit at the end, and puts that string into
 *   the given GValue
 *
 *  Note: yes, it's horrible. So what? Shoot me. It's fast.
 *        A LOT faster than snprintf(). Multiply by number of
 *        visible downloads...
 *
 ******************************************************************************/

static inline void
gui_download_list_value_set_uint_string (GValue *value, guint val, gchar unit)
{
	static gchar  *pos, num[6]; /* all 0x00 */

	/* This is ca. 12x faster than printf() Note: '0' = 0x30 */
	num[0] = ( (val/1000)      | 0x30);
	num[1] = (((val%1000)/100) | 0x30);
	num[2] = (((val%100)/10)   | 0x30);
	num[3] = ( (val%10)        | 0x30);
	num[4] = unit;
	/* num[5] = 0x00;  due to static allocation */
	pos = num;
	if (*pos == '0') /* num[0] */
		++pos;
	if (*pos == '0') /* num[1] */
		++pos;
	if (*pos == '0') /* num[2] */
		++pos;

	g_value_set_string (value, pos);
}

/******************************************************************************
 *
 *  gui_download_list_value_set_float_string
 *
 *  Transform the given value into a float string with one fraction
 *   digit (e.g. 23456 => 2345.6 ) and an optional unit at the end,
 *   and puts that string into the given value.
 *
 *  Note: yes, it's horrible. So what? Shoot me. It's fast.
 *        A LOT faster than snprintf(). Multiply by number of
 *        visible downloads...
 *
 ******************************************************************************/

static inline void
gui_download_list_value_set_float_string (GValue *value, guint val, gchar unit, gboolean twodigits)
{
	gchar  *pos, num[16];
	guint   n = 0;

	num[n++] = ((val/10000) > 9) ? '1' : '0';

//	num[n++] = ( (val/10000)       | 0x30);  /* X000.0  or X00.00 */
	num[n++] = (((val%100000)/10000) | 0x30);  /* X000.0  or X00.00 */
	num[n++] = (((val%10000) /1000)  | 0x30);  /* 0X00.0  or 0X0.00 */
	num[n++] = (((val%1000)  /100)   | 0x30);  /* 00X0.0  or 00X.00 */

	if (!twodigits)
	{
		num[n++] = (((val%100)/10)     | 0x30);  /* 000X.0 */
		num[n++] = '.';
	}
	else
	{
		num[n++] = '.';
		num[n++] = (((val%100)/10)     | 0x30);  /* 000.X0 */
	}
	num[n++] = ( (val%10)          | 0x30);  /* 0000.X  or 000.0X*/
	num[n++] = unit;
	num[n++] = 0x00;

	pos = num;

	while (*pos == '0' && *(pos+1) != 0x00 && *(pos+1) != '.')
		++pos;

	g_value_set_string (value, pos);
}


/******************************************************************************
 *
 *   gui_download_list_get_dot
 *
 ******************************************************************************/

static inline IconName
gui_download_list_get_dot (GuiDownload *dl)
{
	gfloat fpercent;
	guint  percent;

	switch (dl->status)
	{
		case STATUS_PAUSED:
		case STATUS_PAUSING:
		case STATUS_RESUMING:
			return ICON_DL_STATUS_PAUSED;

		case STATUS_QUEUED:
			return ICON_DL_STATUS_PAUSED;

		case STATUS_HASHING:
		case STATUS_HASHING2:
			return ICON_NONE;

		case STATUS_COMPLETE:
			return ICON_DL_STATUS_AVAIL_FULL;

		default:
		break;
	}

	if ( dl->avail >= 100 )
		return ICON_DL_STATUS_AVAIL_FULL;

	if ( dl->sources == 0  ||  dl->avail == 0 )
		return ICON_DL_STATUS_ERROR;	/* colour of 'not available' = colour of 'error' */

	fpercent = (dl->transfered * 100.0) / ( dl->size * 1.0 );
	percent  = (gint) fpercent;

	if ( percent == 100  &&  fpercent < 100.0 )
		percent=99;

	if ( dl->avail <= percent )
		return ICON_DL_STATUS_AVAIL_LOW;

	return ICON_DL_STATUS_AVAIL_HIGH;
}

/******************************************************************************
 *
 *  gui_download_list_get_value
 *
 ******************************************************************************/

static void
gui_download_list_get_value (GtkTreeModel *tree_model,
                             GtkTreeIter  *iter,
                             gint          column,
                             GValue       *value)
{
	GuiDownloadList  *list = (GuiDownloadList*) tree_model;
	GuiDownload      *file;

	g_return_if_fail (GUI_IS_DOWNLOAD_LIST (tree_model));
	g_return_if_fail (iter != NULL);
	g_return_if_fail (iter->stamp == list->stamp );
	g_return_if_fail (column < GUI_DOWNLOAD_LIST(tree_model)->n_columns);

	g_value_init (value, GUI_DOWNLOAD_LIST(tree_model)->column_types[column]);

	file = (GuiDownload*) iter->user_data;

	if (!file)
		return;

	g_assert ( file->pos < list->num_rows );

	switch (column)
	{
		case GUI_DOWNLOAD_LIST_DOT:
			g_value_set_object(value, get_icon(gui_download_list_get_dot(file)));
			break;

		case GUI_DOWNLOAD_LIST_NAME:
		{
			if ((list->flags & GUI_DOWNLOAD_LIST_FLAG_HIDE_JUNK) == 0)
			{
				g_value_set_string(value, (file->name_utf8) ? file->name_utf8 : "");
				break;
			}

			if (!file->name_nojunk_utf8)
			{
				gchar *name_no_junk;
				gchar *nojunk_nocase;

				name_no_junk = g_strdup(file->name);
				remove_junk_from_filename (name_no_junk);
				file->name_nojunk_utf8 = TO_UTF8(name_no_junk);

				nojunk_nocase = g_utf8_casefold(file->name_nojunk_utf8,-1);
				file->name_nojunk_collate_key = g_utf8_collate_key(nojunk_nocase,-1);
				g_free(name_no_junk);
				g_free(nojunk_nocase);
			}

			g_value_set_string(value, file->name_nojunk_utf8);
			break;
		}

		case GUI_DOWNLOAD_LIST_SIZE_STR:
			g_value_set_string(value, file->size_str);
			break;

		case GUI_DOWNLOAD_LIST_STATUS_STR:

			if (file->status_completing_string == NULL)
				g_value_set_string(value, list->status_utf8[MIN(file->status, STATUS_LAST)]);
			else
				g_value_set_string(value, file->status_completing_string);

			break;

		case GUI_DOWNLOAD_LIST_TRANSFERED_STR:
		{
			if (file->done)
				break;

			if ((list->flags & GUI_DOWNLOAD_LIST_FLAG_TRANS_PERC) != 0)
			{
				gui_download_list_value_set_uint_string (value, (guint)(file->transfered_percent*100.0), '%');
				break;
			}

			if ( file->transfered < 1024 )
				gui_download_list_value_set_uint_string (value, file->transfered, 'b');
			else if ( file->transfered < (1024*1024) )
				gui_download_list_value_set_uint_string (value, (file->transfered >> 10) , 'k');
			else if ( file->transfered <= (1024*1024*1024))
				gui_download_list_value_set_float_string (value, (((file->transfered >> 10)*100) >> 10), 'M', TRUE);
			else
				gui_download_list_value_set_float_string (value, (((file->transfered >> 20)*10) >> 10), 'G', FALSE);

			break;
		}

		case GUI_DOWNLOAD_LIST_LEFT_STR:
		{
			if (file->done || file->left == G_MAXUINT)
				break;

			if (file->left < 1024)
				gui_download_list_value_set_uint_string (value, file->left, 'b');
			else if (file->left < (1024*1024))
				gui_download_list_value_set_uint_string (value, (file->left >> 10) , 'k');
			else if (file->left <= (1024*1024*1024))
				gui_download_list_value_set_float_string (value, (((file->left >> 10)*100) >> 10), 'M', TRUE);
			else
				gui_download_list_value_set_float_string (value, (((file->left >> 20)*10) >> 10), 'G', FALSE);

			break;
		}

		case GUI_DOWNLOAD_LIST_SPEED_STR:
		{
			if ( (file->speed > 0.0  ||  file->status == STATUS_DOWNLOADING) && !file->done )
				gui_download_list_value_set_float_string (value, (guint)(file->speed*10.0), 0x00, FALSE);
			else
				g_value_set_string(value, "");

			break;
		}

		case GUI_DOWNLOAD_LIST_TIME_LEFT_STR:
		{
			if ( file->speed < 0.05  ||  file->status != STATUS_DOWNLOADING || file->done )
				break;

			if ( file->transfered > file->size )
			{
				g_value_set_string(value, UTF8_SHORT_PRINTF(_("not long")));     /* TODO: make utf8 once on list creation */
				break;
			}

			if ( file->secs_left >= 3600 )
				gui_download_list_value_set_uint_string (value, file->secs_left/3600, 'h');
			else if ( file->secs_left >= 60 )
				gui_download_list_value_set_uint_string (value, file->secs_left/60, 'm');
			else if (file->secs_left > 0)
				gui_download_list_value_set_uint_string (value, file->secs_left, 's');
			else
				g_value_set_string(value, "");

			break;
		}

		case GUI_DOWNLOAD_LIST_PRIORITY_STR:
		{
			/* display no priority if DL is done */
			if (!file->done)
				g_value_set_string (value, misc_get_prio_string(file->priority));

			break;
		}

		case GUI_DOWNLOAD_LIST_GAPLIST:
		{
			if (file->done)
				g_value_set_pointer(value, NULL);
			else
				g_value_set_pointer(value, file->gaps_data);

			break;
		}

		case GUI_DOWNLOAD_LIST_PAUSED_BOOL:
		{
			g_value_set_boolean(value, (file->status == STATUS_PAUSED));
			break;
		};

		case GUI_DOWNLOAD_LIST_SOURCES_STR:
		{
			if (!file->done)
				gui_download_list_value_set_uint_string (value, file->sources, 0x00);

			break;
		};

#define SECS_TWO_YEARS   (60*60*24*365*2)
#define SECS_ONE_YEAR    (60*60*24*365  )
#define SECS_ONE_DAY     (60*60*24      )
#define SECS_ONE_HOUR    (60*60         )

		case GUI_DOWNLOAD_LIST_LAST_DL_STR:
		case GUI_DOWNLOAD_LIST_LAST_COMPLETE_STR:
		{
			static gchar  *now_utf8, *never_utf8, *never_utf8_red; /* all NULL */
			time_t         lastseen, delta_t;

			/* display nothing if DL is done */
			if (file->done)
				break;

			if (!now_utf8)
			{
				now_utf8       = TO_UTF8(_("now"));
				never_utf8     = TO_UTF8(_("never"));
				never_utf8_red = g_strdup(UTF8_SHORT_PRINTF("<span color='red'>%s</span>", _("never")));
			}

			if (column == GUI_DOWNLOAD_LIST_LAST_DL_STR)
				lastseen = (file->attributes)->last_downloading;
			else
				lastseen = (file->attributes)->last_complete; /* GUI_DOWNLOAD_LIST_LAST_COMPLETE_STR */

			delta_t = time(NULL) - lastseen;

			if ( delta_t >= 0  &&  delta_t <= 6 )
			{
				g_value_set_string (value, now_utf8);
				break;
			}

			/* more than 2 years ago? => never */
			if ( delta_t > SECS_TWO_YEARS )
			{
				if ( file->status == STATUS_PAUSED || file->status == STATUS_HASHING ) /* not red if paused or hashing */
					g_value_set_string (value, never_utf8);
				else
					g_value_set_string (value, never_utf8_red);

				break;
			}

			/* make this as fast as possible (who cares about correct rounding anyway?) */
			if ( delta_t >= SECS_ONE_YEAR )
				gui_download_list_value_set_float_string (value, (delta_t*10)/SECS_ONE_YEAR, 'y', FALSE);
			else if ( delta_t >= SECS_ONE_DAY )
				gui_download_list_value_set_float_string (value, (delta_t*10)/SECS_ONE_DAY, 'd', FALSE);
			else if ( delta_t >= SECS_ONE_HOUR )
				gui_download_list_value_set_float_string (value, (delta_t*10)/SECS_ONE_HOUR, 'h', FALSE);
			else if ( delta_t >= 60 )
				gui_download_list_value_set_float_string (value, (delta_t*10)/60, 'm', FALSE);
			else /* something between 0 and 60 seconds */
				gui_download_list_value_set_uint_string (value, delta_t, 's');

			break;
		};

		case GUI_DOWNLOAD_LIST_TYPE_STR:
			g_value_set_string (value, file->type);
			break;

		case GUI_DOWNLOAD_LIST_FORMAT_STR:
			g_value_set_string (value, file->ext);
			break;

	}
}

/******************************************************************************
 *
 *  gui_download_list_iter_next
 *
 ******************************************************************************/

static gboolean
gui_download_list_iter_next (GtkTreeModel  *tree_model,
                             GtkTreeIter   *iter)
{
	GuiDownloadList *list = (GuiDownloadList*) tree_model;
	GuiDownload     *dl, *nextdl;
	guint            i,old;

	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST (tree_model), FALSE);

	if(!iter)
		return FALSE;

	if(!iter->user_data)
		return FALSE;

	g_return_val_if_fail ( iter->stamp == list->stamp, FALSE );

	dl = (GuiDownload *) iter->user_data;

	old = dl->pos;

	i = old + 1;

	if(i >= list->num_rows)
		return FALSE;

	nextdl = list->rows[i];

	g_assert ( nextdl != NULL && nextdl->pos == i );

	iter->stamp     = list->stamp;
	iter->user_data = nextdl;

	return TRUE;
}


/******************************************************************************
 *
 *  gui_download_list_iter_children
 *
 ******************************************************************************/

static gboolean
gui_download_list_iter_children (GtkTreeModel *tree_model,
                                 GtkTreeIter  *iter,
                                 GtkTreeIter  *parent)
{
	GuiDownloadList  *list = (GuiDownloadList*) tree_model;

	g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST (tree_model), FALSE );
	g_return_val_if_fail (iter->stamp == list->stamp, FALSE );

	/* this is a list, nodes have no children */
	if (parent)
		return FALSE;

	/* No elements? => no children */
	if (list->num_rows == 0)
		return FALSE;

	/* Return first item in list */
	iter->stamp      = list->stamp;
	iter->user_data  = list->rows[0];

	return TRUE;
}


/******************************************************************************
 *
 *  gui_download_list_iter_has_child
 *
 ******************************************************************************/

static gboolean
gui_download_list_iter_has_child (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter)
{
	return FALSE;
}


/******************************************************************************
 *
 *  gui_download_list_iter_n_children
 *
 ******************************************************************************/

static gint
gui_download_list_iter_n_children (GtkTreeModel *tree_model,
                                   GtkTreeIter  *iter)
{
	GuiDownloadList  *list = (GuiDownloadList*) tree_model;

	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST (tree_model), -1);
	g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);

	if (!iter)
		return list->num_rows;

	return 0;
}

/******************************************************************************
 *
 *  gui_download_list_iter_nth_child
 *
 ******************************************************************************/
static gboolean
gui_download_list_iter_nth_child (GtkTreeModel *tree_model,
                                  GtkTreeIter  *iter,
                                  GtkTreeIter  *parent,
                                  gint          n)
{
	GuiDownloadList  *list = (GuiDownloadList *) tree_model;
	GuiDownload      *dl;

	g_return_val_if_fail (GUI_IS_DOWNLOAD_LIST (tree_model), FALSE);

	if(parent)
		return FALSE;

	g_assert ( n < list->num_rows );

	dl = list->rows[n];

	g_assert( dl != NULL && dl->pos == n);

	iter->stamp = list->stamp;
	iter->user_data = dl;

	return TRUE;
}


/******************************************************************************
 *
 *  gui_download_list_iter_parent
 *
 ******************************************************************************/

static gboolean
gui_download_list_iter_parent (GtkTreeModel *tree_model,
                               GtkTreeIter  *iter,
                               GtkTreeIter  *child)
{
  return FALSE;
}


/******************************************************************************
 *
 *  gui_download_list_sortable_get_sort_column_id
 *
 ******************************************************************************/

static gboolean
gui_download_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
                                               gint            *sort_col_id,
                                               GtkSortType     *order)
{
	GuiDownloadList *gui_download_list = (GuiDownloadList*) sortable;

	g_return_val_if_fail ( sortable != NULL        , FALSE );
	g_return_val_if_fail ( GUI_IS_DOWNLOAD_LIST(sortable), FALSE );

	if (sort_col_id)
		*sort_col_id = gui_download_list->sort_id;

	if (order)
		*order =  gui_download_list->sort_order;

	return TRUE;
}


/******************************************************************************
 *
 *  gui_download_list_sortable_set_sort_column_id
 *
 ******************************************************************************/

static void
gui_download_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
                                               gint             sort_col_id,
                                               GtkSortType      order)
{
	GuiDownloadList *gui_download_list = (GuiDownloadList*) sortable;

	g_return_if_fail ( sortable != NULL         );
	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(sortable) );

	if (gui_download_list->sort_id == sort_col_id && gui_download_list->sort_order == order)
		return;

	gui_download_list->sort_id = sort_col_id;
	gui_download_list->sort_order  = order;

	gui_download_list_resort(gui_download_list);

	gtk_tree_sortable_sort_column_changed(sortable);
}


/******************************************************************************
 *
 *  gui_download_list_sortable_set_sort_func
 *
 ******************************************************************************/

static void
gui_download_list_sortable_set_sort_func (GtkTreeSortable        *sortable,
                                          gint                    sort_col_id,
                                          GtkTreeIterCompareFunc  sort_func,
                                          gpointer                user_data,
                                          GtkDestroyNotify        destroy_func)
{
	g_warning ("%s is not supported by the gui_fileList model.\n", __FUNCTION__);
}


/******************************************************************************
 *
 *  gui_download_list_sortable_set_default_sort_func
 *
 ******************************************************************************/

static void
gui_download_list_sortable_set_default_sort_func (GtkTreeSortable        *sortable,
                                                  GtkTreeIterCompareFunc  sort_func,
                                                  gpointer                user_data,
                                                  GtkDestroyNotify        destroy_func)
{
	g_warning ("%s is not supported by the gui_fileList model.\n", __FUNCTION__);
}


/******************************************************************************
 *
 *  gui_download_list_sortable_has_default_sort_func
 *
 ******************************************************************************/

static gboolean
gui_download_list_sortable_has_default_sort_func (GtkTreeSortable *sortable)
{
	return FALSE;
}


/******************************************************************************
 *
 *  gui_download_list_compare_records_get_status_rank
 *
 *  returns a 'ranking' for a status. The 'better' the status,
 *   the higher the returned value
 *
 ******************************************************************************/

static inline gint
gui_download_list_compare_records_get_status_rank (guint status)
{
	static gint  status_value_map[STATUS_LAST+1];
	const  gint  status_ranking[] = { STATUS_HASHING, STATUS_HASHING2, STATUS_ERRLOADING, STATUS_IDS,
	                                  STATUS_CANCELLING, STATUS_PAUSED, STATUS_PAUSING, STATUS_RESUMING, STATUS_QUEUED,
	                                  STATUS_NOSRCS, STATUS_LOOKING, STATUS_TRANSFERRING, STATUS_DOWNLOADING,
	                                  STATUS_ERRHASHING, STATUS_CORRUPTED, STATUS_COMPLETING, STATUS_COMPLETE,
	                                  STATUS_DONE, -1 }; /* the different status from 'worst' to 'best' */

	if (status_value_map[0] == 0)
	{
		gint i;

		for (i=0;  status_ranking[i] >= 0; ++i)
			status_value_map[status_ranking[i]] = i + 1; /* to avoid 0 value */

		/* To avoid jumpy reordering when pausing/resuming */
		status_value_map[STATUS_PAUSING]  = status_value_map[STATUS_PAUSED];
		status_value_map[STATUS_RESUMING] = status_value_map[STATUS_LOOKING];
	}

	if (status < STATUS_LAST)
		return status_value_map[status];

	g_return_val_if_reached(0);
}

/******************************************************************************
 *
 *  gui_download_list_compare_records
 *
 ******************************************************************************/

static gint
gui_download_list_compare_records (gint sort_id, GuiDownload *a, GuiDownload *b, GuiDownloadListFlags flags)
{
    switch(sort_id)
    {
      case GUI_DOWNLOAD_SORT_ID_NONE:
        return 0;

      case GUI_DOWNLOAD_SORT_ID_NAME:
			{
				const gchar  *namea, *nameb;

				/* use name as secondary sort criterion */
				if ((flags & GUI_DOWNLOAD_LIST_FLAG_HIDE_JUNK))
				{
					namea = a->name_nojunk_collate_key;
					nameb = b->name_nojunk_collate_key;
				}
				else
				{
					namea = a->name_collate_key;
					nameb = b->name_collate_key;
				}

				if ((namea)&&(nameb))
					return strcmp(namea,nameb);

				return 0;
			}

      case GUI_DOWNLOAD_SORT_ID_STATUS:
			{
				gint sranka = gui_download_list_compare_records_get_status_rank(a->status);
				gint srankb = gui_download_list_compare_records_get_status_rank(b->status);

        if (sranka == srankb)
          return 0;

        return (sranka > srankb) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_SIZE:
      {
        if (a->size == b->size)
          return 0;

        return (a->size > b->size) ? 1 : -1;
      }

      case GUI_DOWNLOAD_SORT_ID_TRANSFERED:
			{
				if (flags & GUI_DOWNLOAD_LIST_FLAG_TRANS_PERC)
				{
    	    if (a->transfered_percent == b->transfered_percent)
  	        return 0;

	        return (a->transfered_percent > b->transfered_percent) ? 1 : -1;
				}
				else
				{
    	    if (a->transfered == b->transfered)
  	        return 0;

	        return (a->transfered > b->transfered) ? 1 : -1;
				}
			}

      case GUI_DOWNLOAD_SORT_ID_LEFT:
			{
        if (a->left == b->left)
 	        return 0;

   	    return (a->left > b->left) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_SPEED:
			{
        if (a->speed == b->speed)
				{
					/* we want 0.0 Looking... records sorted before 0.0 Paused records */
 	        return gui_download_list_compare_records (GUI_DOWNLOAD_SORT_ID_STATUS, a, b, flags);
				}

   	    return (a->speed > b->speed) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_TIME_LEFT:
			{
        if (a->secs_left == b->secs_left)
				{
					/* we want 0.0 Looking... records sorted before 0.0 Paused records */
 	        return gui_download_list_compare_records (GUI_DOWNLOAD_SORT_ID_STATUS, a, b, flags);
				}

   	    return (a->secs_left > b->secs_left) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_PRIORITY:
			{
        if (a->priority == b->priority)
 	        return 0;

   	    return (a->priority > b->priority) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_AVAILABILITY:
			{
        if (a->avail == b->avail)
 	        return 0;

   	    return (a->avail > b->avail) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_SOURCES:
			{
        if (a->sources == b->sources)
 	        return 0;

   	    return (a->sources > b->sources) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_LAST_DL:
			{
				g_assert ( a->attributes != NULL && b->attributes != NULL );

        if ((a->attributes)->last_downloading == (b->attributes)->last_downloading)
 	        return 0;

   	    return ((a->attributes)->last_downloading > (b->attributes)->last_downloading) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_LAST_COMPLETE:
			{
				g_assert ( a->attributes != NULL && b->attributes != NULL );

        if ((a->attributes)->last_complete == (b->attributes)->last_complete)
 	        return 0;

   	    return ((a->attributes)->last_complete > (b->attributes)->last_complete) ? 1 : -1;
			}

      case GUI_DOWNLOAD_SORT_ID_TYPE:
				return g_ascii_strcasecmp(a->type, b->type);

      case GUI_DOWNLOAD_SORT_ID_FORMAT:
				return g_ascii_strcasecmp(a->ext, b->ext);
    }

	g_return_val_if_reached(0);
}


/******************************************************************************
 *
 *  gui_download_list_sortable_set_default_sort_func
 *
 ******************************************************************************/

static gint
gui_download_list_qsort_compare_func (GuiDownload **a, GuiDownload **b, GuiDownloadList *list)
{
	gint ret;

	g_assert ((a) && (b) && (list));

	ret = gui_download_list_compare_records(list->sort_id, *a, *b, list->flags);

	/* If both records are equal, sort by hash to avoid 'jumping' of records */
	if (ret == 0)
	{
		if (*a != *b)
			ret = (*((guint32*)((*a)->hash)) > *((guint32*)((*b)->hash))) ? 1 : -1 ;
		/* else ret stays 0 */
	}

	/* Swap -1 and 1 if sort order is reverse */
	if (ret != 0  &&  list->sort_order == GTK_SORT_DESCENDING)
		ret = (ret < 0) ? 1 : -1;

	return ret;
}


/******************************************************************************
 *
 *  gui_download_list_resort_real
 *
 ******************************************************************************/

static gboolean
gui_download_list_resort_real (GuiDownloadList *list)
{
	GtkTreePath *path;
	gint        *neworder, i;

	g_return_val_if_fail ( list != NULL, FALSE );
	g_return_val_if_fail ( GUI_IS_DOWNLOAD_LIST(list), FALSE );

	list->resort_timeout = 0;

	if (list->sort_id < 0) /* no sorting? */
		return FALSE;

	if (list->num_rows == 0)
		return FALSE;

	/* resort */
	g_qsort_with_data(list->rows,
	                  list->num_rows,
	                  sizeof(GuiDownload*),
	                  (GCompareDataFunc) gui_download_list_qsort_compare_func,
	                  list);

	/* let other objects know about the new order */
	neworder = g_new0(gint, list->num_rows);

	for (i = 0; i < list->num_rows; ++i)
	{
		/* yeah! I was right and the API docs were wrong ;) */
		neworder[i] = (list->rows[i])->pos;
		(list->rows[i])->pos = i;
	}

	path = gtk_tree_path_new();

	gtk_tree_model_rows_reordered(GTK_TREE_MODEL(list), path, NULL, neworder);

	gtk_tree_path_free(path);
	g_free(neworder);

	return FALSE;
}

/******************************************************************************
 *
 *  gui_download_list_resort
 *
 *  Do the actual resorting in an idle timeout,
 *   so that we can call functions that will lead
 *   to resorting (e.g. new_download) multiple times
 *   without actually resulting in multiple resortings.
 *
 ******************************************************************************/

static void
gui_download_list_resort (GuiDownloadList *list)
{
	g_return_if_fail (GUI_IS_DOWNLOAD_LIST(list));

	if (list->resort_timeout == 0)
	{
		list->resort_timeout = g_idle_add((GSourceFunc) gui_download_list_resort_real, list);
	}
}

/*****************************************************************************
 *
 *   gui_download_list_append_file
 *
 *****************************************************************************/

static void
gui_download_list_append_file (GuiDownloadList *list, GuiDownload *file)
{
	GtkTreeIter   iter;
	GtkTreePath  *path;
	gulong        newsize;
	guint         pos;

	g_return_if_fail (GUI_IS_DOWNLOAD_LIST(list));
	g_return_if_fail (file != NULL);

	pos = list->num_rows;

	list->num_rows++;

	newsize = (list->num_rows + 1) * sizeof(GuiDownload*); /* have terminating NULL */

	list->rows = g_realloc(list->rows, newsize);

	list->rows[pos] = file;
	file->pos = pos;

	list->rows[pos+1] = NULL;

	/* inform the tree view and other interested objects
	 *  (e.g. tree row references) that we have inserted
	 *  a new row, and where it was inserted */

	path = gtk_tree_path_new();
	gtk_tree_path_append_index(path, file->pos);

	gui_download_list_get_iter(GTK_TREE_MODEL(list), &iter, path);

	gtk_tree_model_row_inserted(GTK_TREE_MODEL(list), path, &iter);

	gtk_tree_path_free(path);
}


/******************************************************************************
 *
 *   gui_download_list_get_download_from_path
 *
 ******************************************************************************/

GuiDownload *
gui_download_list_get_download_from_path (GuiDownloadList *list, GtkTreePath *path)
{
	GtkTreeIter iter;

	g_return_val_if_fail ( list != NULL, NULL );
	g_return_val_if_fail ( path != NULL, NULL );

	if (gui_download_list_get_iter(GTK_TREE_MODEL(list), &iter, path))
		return (GuiDownload*) iter.user_data;

	return NULL;
}


/******************************************************************************
 *
 *   gui_download_list_init_download_set_tempfilename
 *
 *   Different core version unfortunately give us different things as
 *    the temp file string. This function sets a full path temp file
 *    string (the one used for previews) based on the core version detected.
 *
 ******************************************************************************/

static void
gui_download_list_init_download_set_tempfilename (GuiDownload *file, const gchar *tfn)
{
	guint n;

	if (!tfn)
		return;

	/* This will only work for non-ancient cores, ie. v59-v54, and >= v1.0
	 *
	 * temp file is usually /tmpdir/filename/1.1.part, but can be
	 *      any x.1.part in case of old v48 imports. (TODO: ignore in future releases)
	 *      Therefore: check if 1.1.part exists, and if not read dir and find base number.
	 *      Can also be /tmpdir/x.part in case of old (pre-v48) partstyle, if configured in pref.met */
		
	/* Special case: some people run the core on a remote machine, but have the temp
	 *  folder mounted on the local machine. We allow them to override the temp path
	 *  specified by the core, so they can preview downloads anyway. */
	if (!gui_core_conn_is_local(core) && opt_get_bool(OPT_GUI_OVERRIDE_TEMP))
	{
		const gchar *mounted_temppath;

		mounted_temppath = opt_get_str(OPT_GUI_OVERRIDE_TEMP_PATH);
		
		if (g_file_test (mounted_temppath, G_FILE_TEST_EXISTS) 
		 && g_file_test (mounted_temppath, G_FILE_TEST_IS_DIR))
		{
			status_message_blue(_("GUI: Locally mounted temp folder override path: %s\n"), mounted_temppath);
			
			n = strlen (mounted_temppath);
			if (mounted_temppath[n-1] != '/' 
			 && mounted_temppath[n-1] != '\\')
			{
				gchar *tmp = g_strconcat(mounted_temppath, G_DIR_SEPARATOR_S, NULL);
				opt_set_str (OPT_GUI_OVERRIDE_TEMP_PATH, tmp);
				mounted_temppath = opt_get_str (OPT_GUI_OVERRIDE_TEMP_PATH);
				g_free (tmp);
			}
			tfn = mounted_temppath;
		}
		else
		{
			status_warning(_("GUI: Cannot override temp path for previews. %s is not a directory or does not exist.\n"), mounted_temppath);
		}
	}

	/* end special case */

	n = strlen(tfn);
	if (tfn[n-1] != '/'  &&  tfn[n-1] != '\\')   /* not a directory path */
	{
		if (!gui_core_conn_is_local(core) || g_file_test(tfn,G_FILE_TEST_EXISTS))
		{
			file->previewname = g_strdup(tfn);
			return;
		}
	}

	/* okay, we did get a directory path as temp filename string */

	file->previewname = g_strdup_printf("%s%s%c1.1.part", tfn, file->name, G_DIR_SEPARATOR);
	if ((gui_core_conn_is_local(core) || opt_get_bool(OPT_GUI_OVERRIDE_TEMP)) && !g_file_test(file->previewname,G_FILE_TEST_EXISTS))
	{
		gchar *dname;
		GDir  *dir;

		G_FREE(file->previewname); /* sets it to NULL as well */

		dname = g_strconcat(tfn, file->name, NULL);
				
		dir = g_dir_open(dname, 0, NULL);
		if (dir)
		{
			const gchar *dentry;
			while ( (dentry = g_dir_read_name(dir)) && (!file->previewname) )
			{
				if ( atoi(dentry) > 0)
					file->previewname = g_strdup_printf("%s%s%c%d.1.part", tfn, file->name, G_DIR_SEPARATOR, atoi(dentry));
			}
			g_dir_close(dir);
		}
		G_FREE(dname);
	}
	/* ELSE: either no local core - then we can't check the constructed
	 *       temp file name anyway (but don't need it anyway), or the name
	 *       checked out fine. */
	
	if (!file->previewname)
	{
		g_warning("Unexpected temp file string in NEW_DOWNLOAD msg: '%s'.\n"
		          "  This means most likely that the GUI could not find or access the temp folder.\n"
		          "  This might happen if the core is on a remote host, in which case you should\n"
		          "   just ignore this message. Or it might happen if the core runs as a different\n"
		          "   user than the GUI. Or it might happen if the core has not created any 1.x.part\n"
		          "   files yet in the download's folder within the temp folder (ie. if the download\n"
		          "   has just been started. Or this might be a download with 'Error Loading' status,\n"
		          "   in which case there is probably a left-over x.part.met file in your temp folder.\n"
		          "   This is regarding the download with the name '%s'.\n", tfn, file->name_utf8);
		file->previewname = g_strdup(tfn);
	}
}


/******************************************************************************
 *
 *  gui_download_list_new_download
 *
 ******************************************************************************/

void
gui_download_list_new_download (GuiDownloadList *list,
                                gconstpointer    hash,
                                const gchar     *name,
                                guint            size,
                                guint            prio,
                                const gchar     *tmpfn,
                                guint            id,
                                gpointer         data)
{
	GuiDownload   *file, *old;
	const gchar   *ext;
	gchar         *name_nocase;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );
	g_return_if_fail ( name != NULL );

	file = g_new0(GuiDownload, 1);

	file->list = list;

	memcpy (file->hash, hash, 16);

	/* Check if we have the hash already. If yes, remove old download */
	old = gui_download_list_get_download_from_hash(list, file->hash);
	if (old)
	{
		g_printerr("Asked to add a download we already have (%s). Replacing old with new.\n", old->name_utf8);
		gui_download_list_remove_download (list, old);
		old = NULL;
	}

	file->size     = size;
	file->size_str = g_strdup(human_size(size));

	file->name       = g_strdup(name);
	file->name_utf8  = TO_UTF8(name);

	name_nocase = g_utf8_casefold(file->name_utf8,-1);
	if (name_nocase)
	{
		file->name_collate_key = g_utf8_collate_key(name_nocase,-1);
		g_free(name_nocase);
	}

	ext = misc_get_extension_from_filename(file->name);
	if (ext)
	{
		file->type = TO_UTF8(misc_get_filetype_from_extension(ext));
		file->ext  = TO_UTF8(ext);
	}

	file->priority = prio;    /* initial priority state */

	if (tmpfn)
	{
		gui_download_list_init_download_set_tempfilename(file, tmpfn);
	}
	else
	{
		g_warning("%s\n", _("This core seems to be old: no temp file info available."));
	}

	/* Cores >= v49.5 have a unique 32-bit ID here now, which
	 *  is used to later map the status msg to the right download. */
	if (id != G_MAXUINT)
	{
		file->id = id;
		list->flags |= GUI_DOWNLOAD_LIST_FLAG_ID;
	}
	else
	{
		g_assert ( (list->flags & GUI_DOWNLOAD_LIST_FLAG_ID) == 0 );

		file->id = list->num_rows; /* old style <= v48.2: ID = index number in DL List   */
	}

	file->attributes = downloadsattriblist_get_record(file->hash);

	g_assert ( file->attributes != NULL );

	file->name_nojunk_utf8 = NULL;  /* filled on demand later */
	file->name_nojunk_collate_key = NULL;

	file->status = STATUS_HASHING;
	file->last_gotten_gaps  = (time_t) 0;

	if ( opt_get_bool(OPT_GUI_DOWNLOADS_BLACKLIST_ON_DOWNLOAD) )
		add_to_blacklist (file->hash, file->size, file->name_utf8);

	gui_download_list_append_file (list, file);

	gui_download_list_resort (list);
}


/******************************************************************************
 *
 *   gui_download_set_sources
 *
 ***
   *
   * MORE THAN 255 SOURCES are a problem, because the GUI-core
   * protocol only provides 8-bit for the number of sources.
   * Currently the core effectively sends us num_sources&0xff, so
   * when there are 257 sources, we'd see a '2'. Which is not really
   * what we want.
   *
   * workaround:
   * if the core is gap-enabled and we show progress bars, then we
   * can scan through the progress bar gaplist and look at the gaps
   * which mark the parts that are available but don't have yet.
   * We will know the number of sources which have this one part
   * (luckily it's a 16-bit value there). Although we cannot deduct
   * from the gaplist how many different sources there are overall,
   * we know can know the minimum number of different sources from
   * the highest number of sources that has one and the same part of
   * the file.
   * Now, if the number of sources as provided by the core is LOWER
   * then the minimum number of sources according to the gaplist,
   * then we can conclude with some probability that we have to add
   * another 256 to get the real number of sources.
   * But of course our gaplist could just be horribly out of date or
   * wrong as well....
   *
   ***/

static inline void
gui_download_set_sources (GuiDownload *dl, guint8 sources_num)
{
	dl->sources = sources_num;

	/* require difference to be at least 20 to avoid wrong
	 * judgements due to less frequent update of the gaplist */
	while ( dl->min_sources > 0  &&  dl->sources < dl->min_sources  &&  (dl->min_sources - dl->sources) >= 20 )
	{
		/* XXX - do we need to specify a minimum threshold value for the
		 * 'gaplist' value (e.g. 64)? */
		dl->sources += 256;
	}
}

/******************************************************************************
 *
 *   downloads_get_percent_completed
 *
 *   If the file system is local, and we are using the new x.part filesystem,
 *    then we can peek at the size of the x.1.part file and see how close it
 *    is to the filesize-to-be. From that we can calculate how much percent
 *    of the completing... process is already done.
 *
 *    NOTE: Of course this does not include the copying process to another
 *          filesystem if incoming/ and temp/ are on different filesystems :-/
 *          (XXX - this might be confusing for the user)
 *
 *    returns gfloat between 0.0 and 100.0 (0.0 on error or if not detectable)
 *
 ******************************************************************************/

static gfloat
gui_download_list_completing_status_get_percent_completed (const GuiDownload *dl)
{
	glong fsize;

	if ( (!dl) || (!dl->previewname) || dl->size == 0)
		return 0.0;

	if (!gui_core_conn_get_core_options(core)->tempdir)
		return 0.0;

	fsize = misc_sys_stat_file_size(dl->previewname);

	if (fsize < 0  ||  fsize > dl->size)
		return 0.0;

	return (((gfloat)fsize / (gfloat)dl->size) * 100.0);
}

/******************************************************************************
 *
 *   gui_download_set_download_status_completing_string
 *
 *   ONLY CALL IF STATUS IS 'completing...' and CORE IS LOCAL!
 *
 ******************************************************************************/

static void
gui_download_set_download_status_completing_string (GuiDownload *dl)
{
	gfloat percentcompleted = 0.0;

	g_return_if_fail ( dl != NULL );

	/* Status is 'Completing...' and core is local */

	percentcompleted = gui_download_list_completing_status_get_percent_completed(dl);

	/*  if we couldn't get a percentage value for some reason ... */
	if ( percentcompleted <= 0.0 )
	{
		G_FREE(dl->status_completing_string);	/* sets it to NULL as well */
		return;
	}

	g_assert ( dl->list != NULL && (dl->list)->status_utf8 != NULL );

	if ( percentcompleted < 100.0 )
	{
		G_FREE(dl->status_completing_string);
		dl->status_completing_string = g_strconcat( (dl->list)->status_utf8[STATUS_COMPLETING],
		                               UTF8_SHORT_PRINTF(_(" (%.0f%% done)"), percentcompleted),
		                               NULL);
		return;
	}

	g_free(dl->status_completing_string); /* NULL is ok here */
	dl->status_completing_string = g_strconcat( (dl->list)->status_utf8[STATUS_COMPLETING],
	                                            UTF8_SHORT_PRINTF("%s", _(" (moving)")),
	                                            NULL);
}

/*****************************************************************************
 *
 *  gui_download_list_set_transfered_from_gaplist
 *
 ******************************************************************************/

static void
gui_download_list_set_transfered_from_gaplist (progressbarGap *pbgaps, guint32 *trans)
{
	gint    i = 0;
	gulong  sum = 0;

	g_return_if_fail ( pbgaps != NULL );
	g_return_if_fail ( trans  != NULL );

	while (pbgaps[i].start!=0 || pbgaps[i].end!=0 || pbgaps[i].status!=0)
	{
		/* >= 11 July 2003 means >= v49.5 */
		if ( pbgaps[i].status == GAPSTATUS_FILLED ||
		       (pbgaps[i].status >= GAPSTATUS_ASKED && gui_core_conn_is_newer_than(core, 11, 7, 2003)))
		{
			gint gaplength = pbgaps[i].end - pbgaps[i].start;

			if ( gaplength > 0 )
				sum = sum + gaplength;
		}
		i++;
	}

	*trans = (guint32) sum;
}

/*****************************************************************************
 *
 *  gui_download_set_download_status
 *
 ******************************************************************************/

void
gui_download_set_download_status (GuiDownload *dl,
                                  guint8       status,
                                  gfloat       speed,
                                  guint32      trans,
                                  guint8       avail,
                                  guint8       sources_num)
{
	guint8      oldstatus;
	time_t      time_now;

	time_now = time(NULL); /* No need to make this lib call 20 times in this functions */

	/* maybe a download has just been removed and we are still getting the status for it */
	if ( dl  == NULL  &&  (time_now - (dl->list)->last_dl_removed) <= 10)
		return;

	g_return_if_fail (dl->attributes != NULL);

	/* If the download is already finished, we are dealing with old and
	 *  delayed status messages. Ignore them. */
	if (dl->done)
		return;

	gui_download_set_sources(dl, sources_num);

	oldstatus      = dl->status;
	dl->status     = status;
	dl->speed      = speed;
	dl->transfered = trans;
	dl->transcore  = trans;
	dl->left       = (dl->transfered > dl->size) ? G_MAXUINT : (dl->size - dl->transfered);
	dl->avail      = avail;

	(dl->attributes)->last_used = time_now;

	/* check if it is time to get new gaps again. The last || statement is
	 *  there to make progress bars grey-ish if we receive first stats after
	 *  having received the first gaps (happens) */
	if ( ( dl->done == FALSE
	         && (oldstatus != STATUS_PAUSED  || dl->last_gotten_gaps == 0)
	         && (time_now - dl->last_gotten_gaps) > (dl->list)->get_gaps_interval )
	     || (oldstatus == STATUS_HASHING  &&  status == STATUS_PAUSED) )
	{
		gui_core_conn_send_get_download_gaps(core, dl->hash);
		dl->last_gotten_gaps = time_now; /* wait normal interval before re-questing gaps if no response from core */
	}

	/* don't change status from 'pausing...' into looking
	 * or 'resuming...' into paused ('cause of delay of core response) */

	if ( oldstatus == STATUS_PAUSING   &&  status == STATUS_LOOKING  )
		dl->status = STATUS_PAUSING;

	if ( oldstatus == STATUS_RESUMING   &&   status == STATUS_PAUSED  )
		dl->status = STATUS_RESUMING;

	if ( status == STATUS_CORRUPTED )
		dl->corrupted = TRUE;

	if ( ( avail == 100 ) && (status == STATUS_LOOKING || status == STATUS_DOWNLOADING || status == STATUS_COMPLETING) )
		(dl->attributes)->last_complete = time_now;


	if ( speed > 0.05 )
		(dl->attributes)->last_downloading = time_now;


	if ( speed < 0.0   ||  speed > 2048.0 )
		dl->speed = 0.0;

	/* Note: Insuff. Disk Space and dlspeed>0 is possible, but probably
	 * wrong most of the time (core bug) (let's not print an error msg though,
	 * as this is a confirmed core bug) */
	if ( status == STATUS_ERRLOADING || status == STATUS_ERRHASHING || status == STATUS_IDS )
		dl->speed = 0.0;

	/* if it is downloading or was in the last 6 seconds, make it 'downloading...' */
	if ( status == STATUS_LOOKING )
	{
		if (  dl->speed > 0.0   ||   (time_now - (dl->attributes)->last_downloading) <= 6  )
			dl->status = STATUS_DOWNLOADING;
	}

	if ( (dl->gaps_data) &&  trans >= dl->size  )
	{
		dl->transfered = dl->transgaplist;
		dl->left = (dl->transfered > dl->size) ? G_MAXUINT : (dl->size - dl->transfered);
	}

	if (dl->status == STATUS_LOOKING || dl->status == STATUS_DOWNLOADING)
	{
		if (dl->speed >= 0.05 && dl->speed < 1024.0) /* assume value is bogus if too high or negative */
		{
			dl->num_speedvals++;
			dl->speedvalsum += dl->speed;
		}
	}

	/* Don't calculate time left based on current speed, but based
	 *  on average speed for the time this download was active
	 *  (including times not downloading). Weigh current speed at 10% */
	if (dl->speed > 0.05  &&  dl->status == STATUS_DOWNLOADING  &&  dl->transfered < dl->size && !dl->done)
	{
		gfloat avgspeed = 0.9 * (dl->speedvalsum / (gfloat) dl->num_speedvals) + 0.1 * dl->speed;

		/* secs_left = bytes_left / (speed_in_bytes_per_sec + 0.001) */
		// dl->secs_left = (gint) (((dl->size - dl->transfered)*1.0) / ((dl->speed*1024.0) + 0.001));

		dl->secs_left = (gint) ((dl->size - dl->transfered)*1.0) / ((avgspeed*1024.0) + 0.001);
	}
	else
	{
		if (dl->transfered >= dl->size || dl->done)
			dl->secs_left = 0;
		else if (dl->status == STATUS_PAUSED)
			dl->secs_left = 0xffffffff;
		else
			dl->secs_left = 0xfffffffe; // TODO: check if this is actually used anywhere
	}

	dl->transfered_percent = (gfloat)dl->transfered / (gfloat)dl->size;

	if (dl->status != STATUS_PAUSED || oldstatus != STATUS_PAUSED)
	{
		if ( dl->status == STATUS_COMPLETING && gui_core_conn_is_local(core))
			gui_download_set_download_status_completing_string(dl);

		gui_download_list_emit_tree_model_signal (SIGNAL_ROW_CHANGED, dl);
	}
}


/******************************************************************************
 *
 *  gui_download_list_update_status
 *
 *  Note:
 *   - core versions up to incl. 48.2 use a 16-bit index number to
 *     map the status info to the DL (e.g. = number in list)
 *   - core versions from 49.5 onwards will send a unique 32-bit ID
 *     (a pointer essentially, so always non-NULL) to map the status
 *     info to a download. That unique ID will also be sent at the end
 *     of the NEW_DOWNLOAD message, so we know which ID goes with which
 *     download.
 *
 ******************************************************************************/

void
gui_download_list_update_status (GuiDownloadList *list,
                                 guint            id,
                                 guint            status,
                                 gfloat           speed,
                                 guint            trans,
                                 guint            avail,
                                 guint            sources,
                                 gpointer         data)
{
	GuiDownload *dl, **pos;

	g_return_if_fail (GUI_IS_DOWNLOAD_LIST(list));

	dl = NULL;

	for ( pos = list->rows; (pos) && (*pos); ++pos )
	{
		if ( (*pos)->id == (guint32) id )
		{
			dl = *pos;
			break;
		}
	}

	g_return_if_fail ( dl != NULL );

	gui_download_set_download_status (dl, status, speed, trans, avail, sources);

	/* No need to resort if the current sort order
	 *  can't be changed by the status message */
	if (   list->sort_id != GUI_DOWNLOAD_SORT_ID_NAME
	    && list->sort_id != GUI_DOWNLOAD_SORT_ID_SIZE
	    && list->sort_id != GUI_DOWNLOAD_SORT_ID_TYPE
	    && list->sort_id != GUI_DOWNLOAD_SORT_ID_FORMAT)
	{
		gui_download_list_resort(list);
	}
}


/******************************************************************************
 *
 *  gui_download_list_gaps_qsort_compare_func
 *
 ******************************************************************************/

static gint
gui_download_list_gaps_qsort_compare_func (progressbarGap *a, progressbarGap *b, progressbarGap *data)
{
	if (a->start > b->start)
		return 1;

	if ( a->start == b->start )
			return 0;

	return -1;
}

/******************************************************************************
 *
 *  gui_download_list_get_download_from_hash
 *
 ******************************************************************************/

GuiDownload *
gui_download_list_get_download_from_hash (GuiDownloadList *list, const guint8 *hash)
{
	GuiDownload **pos;

	/* downloads page disabled? */
	if (list == NULL)
		return NULL;

	g_return_val_if_fail ( GUI_IS_DOWNLOAD_LIST(list), NULL );
	g_return_val_if_fail ( hash != NULL, NULL );

	for ( pos = list->rows;  pos != NULL && *pos != NULL;  ++pos )
	{
		if ( (*pos)->hash[0] == hash[0]  &&  memcmp((*pos)->hash, hash, 16) == 0 )
			return *pos;
	}

	return NULL;
}


/******************************************************************************
 *
 *  gui_download_list_download_gaps
 *
 ******************************************************************************/

void
gui_download_list_download_gaps (GuiDownloadList *list,
                                 const guint8    *hash,
	                               guint            id,
	                               guint            num,
	                               const guint     *startarr,
	                               const guint     *endarr,
	                               const guint     *statusarr,
                                 gpointer         data)
{
	GuiDownload    **pos, *dl = NULL;
	progressbarGap *pbgaps;
	guint           i;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );

	if (hash == NULL)
	{
		for ( pos = list->rows;  pos != NULL && *pos != NULL;  ++pos )
		{
			if ((*pos)->id == id)
			{
				dl = *pos;
				break;
			}
		}
	}
	else
	{
		dl = gui_download_list_get_download_from_hash(list, hash);
	}

	g_return_if_fail ( dl != NULL );  /* TODO: fail silently? */
	g_return_if_fail ( num != (guint) -1    );
	g_return_if_fail ( num <  10000 ); /* better make sure this is a reasonable number */

	dl->min_sources = 0;

	/* only calculate this when needed */
	if (dl->transcore >= dl->size && (dl->gaps_data))
		gui_download_list_set_transfered_from_gaplist(dl->gaps_data, &dl->transgaplist);
	else
		dl->transgaplist = dl->transcore;

	/* allocate NULL-terminated array of progressbarGap structures */
	pbgaps = g_new0 (progressbarGap, (num+1) );

	for (i=0; i < num; ++i)
	{
		pbgaps[i].start  = startarr[i];
		pbgaps[i].end    = endarr[i];
		pbgaps[i].status = statusarr[i];

		pbgaps[i].startrel  = ((gfloat)pbgaps[i].start)/((gfloat)dl->size);
		pbgaps[i].endrel    = ((gfloat)pbgaps[i].end)/((gfloat)dl->size);
		pbgaps[i].widthrel  = pbgaps[i].endrel - pbgaps[i].startrel;

		/* find minimum number of sources for any chunk */
		if (pbgaps[i].status == 0 || pbgaps[i].status == (guint16)-1 || pbgaps[i].status == GAPSTATUS_FILLED )
			continue;

		/* got it or asked for? */
		if ( pbgaps[i].status >= GAPSTATUS_ASKED )
			continue;

		/* means _now_ (after previous checks): available */
		dl->min_sources = MAX (dl->min_sources, pbgaps[i].status);
	}

	/* If it's the first time we get the gaps, refresh display immediately */
	if (!dl->gaps_data)
		gui_download_list_emit_tree_model_signal (SIGNAL_ROW_CHANGED, dl);
	else
		g_free(dl->gaps_data);

	/* sort gaps - TODO: is this necessary? */
	g_qsort_with_data(pbgaps,
	                  num,
	                  sizeof(progressbarGap),
	                  (GCompareDataFunc) gui_download_list_gaps_qsort_compare_func,
	                  pbgaps);

	dl->gaps_data = pbgaps;
	dl->last_gotten_gaps = time(NULL);
}


/******************************************************************************
 *
 *  gui_download_list_download_change_status
 *
 ******************************************************************************/

void
gui_download_list_download_change_status (GuiDownloadList *list, GuiDownload *dl, guint newstatus)
{
	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );
	g_return_if_fail ( dl != NULL );
	g_return_if_fail ( newstatus < STATUS_LAST );

	dl->status = newstatus;

	gui_download_list_emit_tree_model_signal (SIGNAL_ROW_CHANGED, dl);
}


/******************************************************************************
 *
 *  gui_download_list_get_all_downloads
 *
 *  Returns a GList with all (not completed) downloads. List must be
 *   freed with g_list_free() when done.
 *
 ******************************************************************************/

GList *
gui_download_list_get_all_downloads (GuiDownloadList *list)
{
	GuiDownload **pos;
	GList        *glist = NULL;

	g_return_val_if_fail ( GUI_IS_DOWNLOAD_LIST(list), NULL );

	for (pos = list->rows; ((pos) && (*pos));  ++pos)
	{
		if ((*pos)->done == FALSE)
			glist = g_list_append(glist, *pos);
	}

	return glist;
}

/******************************************************************************
 *
 *  gui_download_list_count_completed
 *
 ******************************************************************************/

guint
gui_download_list_count_completed (GuiDownloadList *list)
{
	GuiDownload **pos;
	guint         num = 0;

	g_return_val_if_fail ( GUI_IS_DOWNLOAD_LIST(list), 0);

	for ( pos = list->rows;  pos != NULL && *pos != NULL;  ++pos )
	{
		if ((*pos)->done == TRUE)
			++num;
	}

	return num;
}

/******************************************************************************
 *
 *  gui_download_list_clear_completed
 *
 ******************************************************************************/

void
gui_download_list_clear_completed (GuiDownloadList *list)
{
	GuiDownload **pos;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );

	pos = list->rows;
	while ((pos) && (*pos))
	{
		if ((*pos)->done == TRUE)
		{
			gui_download_list_remove_download(list, *pos);
			pos = list->rows; /* start from beginning, goto alternative */
		}
		else
		{
			++pos;
		}
	}
}


/******************************************************************************
 *
 *  gui_download_list_free_download
 *
 ******************************************************************************/

static void
gui_download_list_free_download (GuiDownload *dl)
{
	g_return_if_fail ( dl != NULL );

	G_FREE(dl->name)
	G_FREE(dl->name_utf8);
	G_FREE(dl->name_collate_key);
	G_FREE(dl->name_nojunk_utf8);
	G_FREE(dl->name_nojunk_collate_key);
	G_FREE(dl->size_str);
	G_FREE(dl->previewname);
	G_FREE(dl->status_completing_string);
	G_FREE(dl->gaps_data);
	G_FREE(dl->ext);
	G_FREE(dl->type);

	memset(dl, 0x00, sizeof(GuiDownload));
	g_free(dl);
}

/******************************************************************************
 *
 *  gui_download_list_remove_download
 *
 ******************************************************************************/

static void
gui_download_list_remove_download (GuiDownloadList *list, GuiDownload *dl)
{
	guint delpos, i;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );
	g_return_if_fail ( dl != NULL );

	gui_download_list_emit_tree_model_signal (SIGNAL_ROW_DELETED, dl);

	delpos = dl->pos;

	gui_download_list_free_download(dl);
	dl = NULL;

	for (i = delpos;  list->rows[i] != NULL;  ++i)
	{
		GuiDownload *next;

		next = list->rows[i+1];
		list->rows[i] = next;
		if (next)
			next->pos = i;
	}

	list->num_rows = list->num_rows - 1;

	g_assert (list->rows[list->num_rows] == NULL);
}



/******************************************************************************
 *
 *  gui_download_list_download_removed
 *
 *  The core tells us that a download is to be removed from
 *   the download list - either because it is complete/corrupted,
 *   or because it has been cancelled.
 *
 ******************************************************************************/

void
gui_download_list_download_removed (GuiDownloadList *list, const guint8 *hash, guint cancelled, gpointer data)
{
	GuiDownload *dl;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );
	g_return_if_fail ( hash != NULL );

	dl = gui_download_list_get_download_from_hash (list, hash);

	if (dl == NULL )
	{
		g_printerr("Asked to remove a download that does not exist?\n");
		return;
	}

	g_return_if_fail ( dl->done == FALSE );

	if (cancelled == 1)
		dl->cancelled = TRUE;

	if (dl->cancelled)
	{
		status_msg ("%s: ed2k://|file|%s|%u|%s|\n", _("GUI: download cancelled"), dl->name_utf8, dl->size, hash_to_hash_str(hash));
		gui_download_list_remove_download (list, dl);
		return;
	}

	status_message_blue (_("----- DOWNLOAD COMPLETE ---------------\n"));
	status_message_blue ("%s\n", dl->name_utf8);


	if (dl->corrupted)
	{
		dl->corrupted = TRUE;

		if (!gui_core_conn_get_core_options(core)->save_corrupted)
			g_printerr (_("has been corrupted - file has been deleted from disk.\n"));
		else
			g_printerr (_("has been corrupted - file has been saved and is in your temp folder.\n"));

		return;
	}

	status_message_blue (_("should have been moved to your incoming folder.\n"));

	exec_command_on_complete_download(dl->name);

	status_message_blue ("---------------------------------------\n");
	statusbar_msg (_(" %s has been completed."), dl->name_utf8);

	dl->done = TRUE;
	dl->speed = 0.0;
	dl->status = (dl->corrupted) ? STATUS_CORRUPTED : STATUS_COMPLETE;
	G_FREE(dl->status_completing_string);

	/* update row */
	gui_download_list_download_change_status (list, dl, dl->status);

	if (opt_get_bool(OPT_GUI_RESUME_ONE_PAUSED_DL_ON_COMPLETE))
		gui_download_list_resume_a_paused_file(list);
}


/******************************************************************************
 *
 *  gui_download_list_pause_and_resume_all
 *
 ******************************************************************************/

void
gui_download_list_pause_and_resume_all (GuiDownloadList *list)
{
	GuiDownload **pos;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );

	for (pos = list->rows;  pos != NULL && *pos != NULL; ++pos)
	{
		if ((*pos)->done == FALSE && ((*pos)->status == STATUS_LOOKING || (*pos)->status == STATUS_DOWNLOADING))
		{
			gui_core_conn_send_pause_download(core, (*pos)->hash);
			(*pos)->status = STATUS_PAUSING;
		}
	}

	if (!gui_core_conn_is_overnet(core) || gui_core_conn_is_hybrid(core))
	{
		gui_core_conn_send_disconnect_from_server(core);
		g_usleep(G_USEC_PER_SEC/2);
		gui_core_conn_send_connect_to_any_server(core);
	}

	for (pos = list->rows;  pos != NULL && *pos != NULL; ++pos)
	{
		/* only resume those we paused before */
		if ((*pos)->done == FALSE && (*pos)->status == STATUS_PAUSING)
			gui_core_conn_send_resume_download(core, (*pos)->hash);
	}

	statusbar_msg ("%s", _(" IP change signalled - paused and resumed all active downloads and changed server."));
}


/******************************************************************************
 *
 *  gui_download_list_resume_a_paused_file
 *
 *  Called after a file has completed. Will try to resume a paused
 *   download, honouring priorities.
 *
 ******************************************************************************/

static gboolean
downloads_resume_a_paused_file_helper(GuiDownloadList *list, guint8 prio)
{
	GuiDownload  **pos;

	for (pos = list->rows;  pos != NULL && *pos != NULL;  ++pos)
	{
		if ((*pos)->status == STATUS_PAUSED  &&  (*pos)->priority == prio)
		{
			gui_core_conn_send_resume_download(core, (*pos)->hash);
			gui_download_list_download_change_status(list, *pos, STATUS_RESUMING);
			status_message_blue(_("Resumed %s.\n"), (*pos)->name_utf8);
			return TRUE;
		}
	}

	return FALSE;
}

static void
gui_download_list_resume_a_paused_file (GuiDownloadList *list)
{
	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );

	if (!downloads_resume_a_paused_file_helper(list,DL_PRIO_HIGHEST))
	{
		if (!downloads_resume_a_paused_file_helper(list,DL_PRIO_HIGH))
		{
			if (!downloads_resume_a_paused_file_helper(list,DL_PRIO_NORMAL))
			{
				if (!downloads_resume_a_paused_file_helper(list,DL_PRIO_LOW))
				{
					status_message_blue(_("No download to resume found.\n"));
				}
			}
		}
	}
}


/******************************************************************************
 *
 *  gui_download_list_set_corrupted_from_filename
 *
 ******************************************************************************/

void
gui_download_list_set_corrupted_from_filename (GuiDownloadList *list, const gchar *fncore)
{
	GuiDownload  **pos;

	g_return_if_fail ( GUI_IS_DOWNLOAD_LIST(list) );

	for (pos = list->rows;  pos != NULL && *pos != NULL;  ++pos)
	{
		if (((*pos)->name) &&  strstr((*pos)->name, fncore)  != NULL)
		{
			(*pos)->corrupted = TRUE;
			gui_download_list_download_change_status(list, *pos, STATUS_CORRUPTED);
			return;
		}
	}

}



