/***************************************************************************
 *            burn-sum.c
 *
 *  ven aoû  4 19:46:34 2006
 *  Copyright  2006  Rouquier Philippe
 *  brasero-app@wanadoo.fr
 ***************************************************************************/

/*
 *  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.
 *
 *  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 Library 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.
 */


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

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>

#include <libgnomevfs/gnome-vfs.h>

#include "burn-job.h"
#include "burn-imager.h"
#include "burn-sum.h"

static void brasero_burn_sum_class_init (BraseroBurnSumClass *klass);
static void brasero_burn_sum_init (BraseroBurnSum *sp);
static void brasero_burn_sum_finalize (GObject *object);
static void brasero_burn_sum_iface_init_image (BraseroImagerIFace *iface);

static BraseroBurnResult
brasero_burn_sum_start (BraseroJob *job,
			int in_fd,
			int *out_fd,
			GError **error);
static BraseroBurnResult
brasero_burn_sum_stop (BraseroJob *job,
		       BraseroBurnResult retval);
static BraseroBurnResult
brasero_burn_sum_get_written (BraseroJob *job,
			      gint64 *written);
static BraseroBurnResult
brasero_burn_sum_get_action_string (BraseroJob *job,
				    BraseroBurnAction action,
				    gchar **string);
static BraseroBurnResult
brasero_burn_sum_set_source (BraseroJob *job,
			     const BraseroTrackSource *source,
			     GError **error);

static BraseroBurnResult
brasero_burn_sum_set_output (BraseroImager *imager,
			     const gchar *output,
			     gboolean overwrite,
			     gboolean clean,
			     GError **error);
static BraseroBurnResult
brasero_burn_sum_set_output_type (BraseroImager *imager,
				  BraseroTrackSourceType type,
				  BraseroImageFormat format,
				  GError **error);
static BraseroBurnResult
brasero_burn_sum_get_track (BraseroImager *imager,
			    BraseroTrackSource **source,
			    GError **error);

struct _BraseroBurnSumPrivate {
	BraseroTrackSource *source;

	gchar *sums_path;
	FILE *file;

	guint32 A;
	guint32 B;
	guint32 C;
	guint32 D;
	guint32 size [2];

	GThread *thread;

	gint64 written_b;
	off_t file_size;

	gint end_id;

	gint sums_ready:1;
	gint overwrite:1;
	gint cancel:1;
	gint clean:1;
};

#if G_BYTE_ORDER == G_BIG_ENDIAN
#define SWAP(num)	GUINT32_SWAP_LE_BE (num)
#else
#define SWAP(num)	num
#endif

#define F(X,Y,Z)			(((X) & (Y)) | ((~X) & (Z)))
#define G(X,Y,Z)			(((X) & (Z)) | ((Y) & (~Z)))
#define H(X,Y,Z)			((X) ^ (Y) ^ (Z))
#define I(X,Y,Z)			((Y) ^ ((X) | (~Z)))

#define ROTATE(num, s)			((num << s) | (num >> (32 - s)))

#define STEP1(A, B, C, D, k, s, i)			\
{							\
	A += F (B, C, D) + SWAP (((guint32*) buffer) [k]) + i;	\
	A = ROTATE (A, s);				\
	A += B;						\
}
#define STEP2(A, B, C, D, k, s, i)			\
{							\
	A += G (B, C, D) + SWAP (((guint32*) buffer) [k]) + i;	\
	A = ROTATE (A, s);				\
	A += B;						\
}
#define STEP3(A, B, C, D, k, s, i)			\
{							\
	A += H (B, C, D) + SWAP (((guint32*) buffer) [k]) + i;	\
	A = ROTATE (A, s);				\
	A += B;						\
}
#define STEP4(A, B, C, D, k, s, i)			\
{							\
	A += I (B, C, D) + SWAP (((guint32*) buffer) [k]) + i;	\
	A = ROTATE (A, s);				\
	A += B;						\
}

#define NB_WORDS			16
#define BLOCK_WORDS_NUM			4
#define BLOCK_SIZE			64

static BraseroJobClass *parent_class = NULL;

GType
brasero_burn_sum_get_type ()
{
	static GType type = 0;

	if(type == 0) {
		static const GTypeInfo our_info = {
			sizeof (BraseroBurnSumClass),
			NULL,
			NULL,
			(GClassInitFunc)brasero_burn_sum_class_init,
			NULL,
			NULL,
			sizeof (BraseroBurnSum),
			0,
			(GInstanceInitFunc)brasero_burn_sum_init,
		};
		static const GInterfaceInfo imager_info =
		{
			(GInterfaceInitFunc) brasero_burn_sum_iface_init_image,
			NULL,
			NULL
		};
		type = g_type_register_static (BRASERO_TYPE_JOB, 
					       "BraseroBurnSum",
					       &our_info,
					       0);
		g_type_add_interface_static (type,
					     BRASERO_TYPE_IMAGER,
					     &imager_info);
	}

	return type;
}

static void
brasero_burn_sum_class_init (BraseroBurnSumClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	BraseroJobClass *job_class = BRASERO_JOB_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);
	object_class->finalize = brasero_burn_sum_finalize;

	job_class->start = brasero_burn_sum_start;
	job_class->stop = brasero_burn_sum_stop;
	job_class->set_source = brasero_burn_sum_set_source;
	job_class->get_written = brasero_burn_sum_get_written;
	job_class->get_action_string = brasero_burn_sum_get_action_string;
}

static void
brasero_burn_sum_iface_init_image (BraseroImagerIFace *iface)
{
//	iface->get_size = brasero_burn_sum_get_size;
	iface->get_track = brasero_burn_sum_get_track;
//	iface->get_track_type = brasero_burn_sum_get_track_type;
	iface->set_output = brasero_burn_sum_set_output;
//	iface->set_append = brasero_burn_sum_set_append;
	iface->set_output_type = brasero_burn_sum_set_output_type;
}

static void
brasero_burn_sum_init (BraseroBurnSum *obj)
{
	obj->priv = g_new0(BraseroBurnSumPrivate, 1);
	/* Initialize private members, etc. */
}

static void
brasero_burn_sum_finalize (GObject *object)
{
	BraseroBurnSum *cobj;
	
	cobj = BRASERO_BURN_SUM(object);

	if (cobj->priv->thread) {
		cobj->priv->cancel = 1;
		g_thread_join (cobj->priv->thread);
		cobj->priv->cancel = 0;
		cobj->priv->thread = NULL;
	}

	if (cobj->priv->end_id) {
		g_source_remove (cobj->priv->end_id);
		cobj->priv->end_id = 0;
	}

	if (cobj->priv->file) {
		fclose (cobj->priv->file);
		cobj->priv->file = NULL;
	}

	if (cobj->priv->sums_path) {
		if (cobj->priv->clean)
			g_remove (cobj->priv->sums_path);

		g_free (cobj->priv->sums_path);
		cobj->priv->sums_path = NULL;
	}

	if (cobj->priv->source) {
		brasero_track_source_free (cobj->priv->source);
		cobj->priv->source = NULL;
	}
		
	g_free (cobj->priv);
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

BraseroBurnSum *
brasero_burn_sum_new ()
{
	BraseroBurnSum *obj;
	
	obj = BRASERO_BURN_SUM (g_object_new (BRASERO_TYPE_BURN_SUM, NULL));
	
	return obj;
}

static void
brasero_burn_sum_process_block_md5 (BraseroBurnSum *sum,
				    guchar *buffer)
{
	guint32 A, B, C, D;

	A = sum->priv->A;
	B = sum->priv->B;
	C = sum->priv->C;
	D = sum->priv->D;

	/* step 1 */
	STEP1 (A, B, C, D, 0, 7, 0xd76aa478);
	STEP1 (D, A, B, C, 1, 12, 0xe8c7b756);
	STEP1 (C, D, A, B, 2, 17, 0x242070db);
	STEP1 (B, C, D, A, 3, 22, 0xc1bdceee);
	STEP1 (A, B, C, D, 4, 7, 0xf57c0faf);
	STEP1 (D, A, B, C, 5, 12, 0x4787c62a);
	STEP1 (C, D, A, B, 6, 17, 0xa8304613);
	STEP1 (B, C, D, A, 7, 22, 0xfd469501);
	STEP1 (A, B, C, D, 8, 7, 0x698098d8);
	STEP1 (D, A, B, C, 9, 12, 0x8b44f7af);
	STEP1 (C, D, A, B, 10, 17, 0xffff5bb1);
	STEP1 (B, C, D, A, 11, 22, 0x895cd7be);
	STEP1 (A, B, C, D, 12, 7, 0x6b901122);
	STEP1 (D, A, B, C, 13, 12, 0xfd987193);
	STEP1 (C, D, A, B, 14, 17, 0xa679438e);
	STEP1 (B, C, D, A, 15, 22, 0x49b40821);

	/* step 2 */
	STEP2 (A, B, C, D, 1, 5, 0xf61e2562);
	STEP2 (D, A, B, C, 6, 9, 0xc040b340);
	STEP2 (C, D, A, B, 11, 14, 0x265e5a51);
	STEP2 (B, C, D, A, 0, 20, 0xe9b6c7aa);
	STEP2 (A, B, C, D, 5, 5, 0xd62f105d);
	STEP2 (D, A, B, C, 10, 9, 0x02441453);
	STEP2 (C, D, A, B, 15, 14, 0xd8a1e681);
	STEP2 (B, C, D, A, 4, 20, 0xe7d3fbc8);
	STEP2 (A, B, C, D, 9, 5, 0x21e1cde6);
	STEP2 (D, A, B, C, 14, 9, 0xc33707d6);
	STEP2 (C, D, A, B, 3, 14, 0xf4d50d87);
	STEP2 (B, C, D, A, 8, 20, 0x455a14ed);
	STEP2 (A, B, C, D, 13, 5, 0xa9e3e905);
	STEP2 (D, A, B, C, 2, 9, 0xfcefa3f8);
	STEP2 (C, D, A, B, 7, 14, 0x676f02d9);
	STEP2 (B, C, D, A, 12, 20, 0x8d2a4c8a);

	/* step 3 */
	STEP3 (A, B, C, D, 5, 4, 0xfffa3942);
	STEP3 (D, A, B, C, 8, 11, 0x8771f681);
	STEP3 (C, D, A, B, 11, 16, 0x6d9d6122);
	STEP3 (B, C, D, A, 14, 23, 0xfde5380c);
	STEP3 (A, B, C, D, 1, 4, 0xa4beea44);
	STEP3 (D, A, B, C, 4, 11, 0x4bdecfa9);
	STEP3 (C, D, A, B, 7, 16, 0xf6bb4b60);
	STEP3 (B, C, D, A, 10, 23, 0xbebfbc70);
	STEP3 (A, B, C, D, 13, 4, 0x289b7ec6);
	STEP3 (D, A, B, C, 0, 11, 0xeaa127fa);
	STEP3 (C, D, A, B, 3, 16, 0xd4ef3085);
	STEP3 (B, C, D, A, 6, 23, 0x04881d05);
	STEP3 (A, B, C, D, 9, 4, 0xd9d4d039);
	STEP3 (D, A, B, C, 12, 11, 0xe6db99e5);
	STEP3 (C, D, A, B, 15, 16, 0x1fa27cf8);
	STEP3 (B, C, D, A, 2, 23, 0xc4ac5665);

	/* step 4 */
	STEP4 (A, B, C, D, 0, 6, 0xf4292244);
	STEP4 (D, A, B, C, 7, 10, 0x432aff97);
	STEP4 (C, D, A, B, 14, 15, 0xab9423a7);
	STEP4 (B, C, D, A, 5, 21, 0xfc93a039);
	STEP4 (A, B, C, D, 12, 6, 0x655b59c3);
	STEP4 (D, A, B, C, 3, 10, 0x8f0ccc92);
	STEP4 (C, D, A, B, 10, 15, 0xffeff47d);
	STEP4 (B, C, D, A, 1, 21, 0x85845dd1);
	STEP4 (A, B, C, D, 8, 6, 0x6fa87e4f);
	STEP4 (D, A, B, C, 15, 10, 0xfe2ce6e0);
	STEP4 (C, D, A, B, 6, 15, 0xa3014314);
	STEP4 (B, C, D, A, 13, 21, 0x4e0811a1);
	STEP4 (A, B, C, D, 4, 6, 0xf7537e82);
	STEP4 (D, A, B, C, 11, 10, 0xbd3af235);
	STEP4 (C, D, A, B, 2, 15, 0x2ad7d2bb);
	STEP4 (B, C, D, A, 9, 21, 0xeb86d391);

	sum->priv->A += A;
	sum->priv->B += B;
	sum->priv->C += C;
	sum->priv->D += D;
}

static BraseroBurnResult
brasero_burn_sum_start_md5 (BraseroBurnSum *sum,
			    const gchar *path,
			    const gchar *graft_path,
			    GError **error)
{
	FILE *file;
	gint read_bytes;
	guchar buffer [BLOCK_SIZE];
	BraseroBurnResult result = BRASERO_BURN_OK;
	
	sum->priv->A = 0x67452301;
	sum->priv->B = 0xefcdab89;
	sum->priv->C = 0x98badcfe;
	sum->priv->D = 0x10325476;
	sum->priv->size [0] = 0;
	sum->priv->size [1] = 0;

	file = fopen (path, "r");
	if (!file) {
		gchar *name;

		name = g_path_get_basename (path);
		g_set_error (error,
			     BRASERO_BURN_ERROR,
			     BRASERO_BURN_ERROR_GENERAL,
			     _("the file %s couldn't be read (%s)"),
			     name,
			     strerror (errno));
		g_free (name);

		return BRASERO_BURN_ERR;
	}

	while (1) {
		if (sum->priv->cancel) {
			fclose (file);
			return BRASERO_BURN_CANCEL;
		}

		read_bytes = fread (buffer, 1, BLOCK_SIZE, file);
		sum->priv->written_b += read_bytes;

		sum->priv->size [0] += read_bytes;
		if (sum->priv->size [0] < read_bytes)
			sum->priv->size [1] ++;

		if (read_bytes != BLOCK_SIZE) {
			gchar *name;

			/* that's either the end or an error */
			if (feof (file))
				break;

			fclose (file);

			name = g_path_get_basename (path);
			g_set_error (error,
				     BRASERO_BURN_ERROR,
				     BRASERO_BURN_ERROR_GENERAL,
				     _("the file %s couldn't be read (%s)"),
				     name,
				     strerror (errno));
			g_free (name);

			return BRASERO_BURN_ERR;
		}

		brasero_burn_sum_process_block_md5 (sum, buffer);
	}

	fclose (file);

	/* process the remaining bytes, pad them and add the size */
	bzero (buffer + read_bytes, BLOCK_SIZE - read_bytes);
	*(buffer + read_bytes) = 0x80;

	if (read_bytes >= 56) {
		brasero_burn_sum_process_block_md5 (sum, buffer);
		bzero (buffer, 56);
	}

	*((guint32 *) (buffer + 56)) = SWAP (sum->priv->size [0] << 3);
	*((guint32 *) (buffer + 60)) = SWAP (sum->priv->size [1] << 3 | sum->priv->size [0] >> 29);

	brasero_burn_sum_process_block_md5 (sum, buffer);

	/* write to the file */
	if (sum->priv->file) {
		gint i, j;
		gint written;

		for (i = 0; i < 4; i ++) {
			guchar *number = NULL;

			switch (i) {
			case 0:
				number = (guchar*) &sum->priv->A;
				break;
			case 1:
				number = (guchar*) &sum->priv->B;
				break;
			case 2:
				number = (guchar*) &sum->priv->C;
				break;
			case 3:
				number = (guchar*) &sum->priv->D;
				break;
			default:
				break;
			}
			
			for (j = 0; j < 4; j ++) {
				gchar md5_checksum [15];

				sprintf (md5_checksum, "%02x", number [j]);
				written = fwrite (md5_checksum,
						  strlen (md5_checksum),
						  1,
						  sum->priv->file);

				if (written != 1) {
					g_set_error (error,
						     BRASERO_BURN_ERROR,
						     BRASERO_BURN_ERROR_GENERAL,
						     _("the md5 file couldn't be written to (%s)"),
						     strerror (errno));

					return BRASERO_BURN_ERR;
				}
			}
		}

		written = fwrite ("  ",
				  2,
				  1,
				  sum->priv->file);

		/* NOTE: we remove the first "/" from path so the file can be
		 * used with md5sum at the root of the disc once mounted */
		written = fwrite (graft_path + 1,
				  strlen (graft_path + 1),
				  1,
				  sum->priv->file);

		if (written != 1) {
			g_set_error (error,
				     BRASERO_BURN_ERROR,
				     BRASERO_BURN_ERROR_GENERAL,
				     _("the md5 file couldn't be written to (%s)"),
				     strerror (errno));

			return BRASERO_BURN_ERR;
		}

		written = fwrite ("\n",
				  1,
				  1,
				  sum->priv->file);
	}

	return result;
}

static BraseroBurnResult
brasero_burn_sum_explore_directory (BraseroBurnSum *sum,
				    const gchar *directory,
				    const gchar *disc_path,
				    GHashTable *excludedH,
				    GError **error)
{
	BraseroBurnResult result = BRASERO_BURN_OK;
	const gchar *name;
	GDir *dir;

	dir = g_dir_open (directory, 0, error);
	if (!dir || *error)
		return BRASERO_BURN_ERR;

	while ((name = g_dir_read_name (dir))) {
		gchar *path;
		gchar *graft_path;

		if (sum->priv->cancel) {
			result = BRASERO_BURN_CANCEL;
			break;
		}

		path = g_build_path (G_DIR_SEPARATOR_S, directory, name, NULL);
		if (g_hash_table_lookup (excludedH, path)) {
			g_free (path);
			continue;
		}

		graft_path = g_build_path (G_DIR_SEPARATOR_S, disc_path, name, NULL);
		if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
			result = brasero_burn_sum_explore_directory (sum,
								     path,
								     graft_path,
								     excludedH,
								     error);
			g_free (path);
			g_free (graft_path);

			if (result != BRASERO_BURN_OK)
				break;

			continue;
		}
			
		result = brasero_burn_sum_start_md5 (sum,
						     path,
						     graft_path,
						     error);
		g_free (graft_path);
		g_free (path);

		if (result != BRASERO_BURN_OK)
			break;
	}
	g_dir_close (dir);

	/* NOTE: we don't care if the file is twice or more on the disc,
	 * that would be too much overhead/memory consumption for something
	 * that scarcely happens and that way each file can be checked later*/

	return result;
}

static gboolean
brasero_burn_sum_clean_excluded_table_cb (gpointer key,
					  gpointer data,
					  gpointer user_data)
{
	if (GPOINTER_TO_INT (data) == 1)
		return TRUE;

	return FALSE;
}

static BraseroBurnResult
brasero_burn_sum_explore_grafts (BraseroBurnSum *sum, GError **error)
{
	GSList *iter;
	GHashTable *excludedH;
	BraseroBurnResult result = BRASERO_BURN_OK;

	/* opens a file for the sums */
	if (!sum->priv->sums_path) {
		int fd;

		sum->priv->sums_path = g_strdup_printf ("%s/"BRASERO_BURN_TMP_FILE_NAME,
							g_get_tmp_dir ());
		fd = g_mkstemp (sum->priv->sums_path);
		sum->priv->file = fdopen (fd, "w");
	}
	else {
		/* we check that this file doesn't already exists */
		if (!sum->priv->overwrite
		&&  g_file_test (sum->priv->sums_path, G_FILE_TEST_EXISTS)) {
			gchar *name;
	
			name = g_path_get_basename (sum->priv->sums_path);
			g_set_error (error,
				     BRASERO_BURN_ERROR,
				     BRASERO_BURN_ERROR_GENERAL,
				     _("\"%s\" already exists"),
				     name);
			g_free (name);

			return BRASERO_BURN_ERR;
		}

		sum->priv->file = fopen (sum->priv->sums_path, "w");
	}

	if (!sum->priv->file) {
		g_set_error (error,
			     BRASERO_BURN_ERROR,
			     BRASERO_BURN_ERROR_GENERAL,
			     _("md5 file couldn't be opened (%s)"),
			     strerror (errno));

		return BRASERO_BURN_ERR;
	}

	/* we fill a hash table with all the files that are excluded globally */
	excludedH = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	for (iter = sum->priv->source->contents.data.excluded; iter; iter = iter->next) {
		gchar *uri;
		gchar *path;
		gchar *escaped_uri;

		/* get the path */
		uri = iter->data;
		escaped_uri = gnome_vfs_escape_host_and_path_string (uri);
		path = gnome_vfs_get_local_path_from_uri (escaped_uri);
		g_free (escaped_uri);

		if (path)
			g_hash_table_insert (excludedH, path, path);
	}

	/* it's now time to start reporting our progress */
	if (brasero_job_task_get_elapsed (BRASERO_JOB (sum), NULL) == BRASERO_BURN_NOT_READY)
		brasero_job_task_start_progress (BRASERO_JOB (sum));

	for (iter = sum->priv->source->contents.data.grafts; iter; iter = iter->next) {
		BraseroGraftPt *graft;
		gchar *escaped_uri;
		gchar *graft_path;
		GSList *excluded;
		gchar *path;
		gchar *uri;

		if (sum->priv->cancel) {
			result = BRASERO_BURN_CANCEL;
			break;
		}

		graft = iter->data;
		if (!graft->uri)
			continue;

		/* add all excluded in the excluded graft hash */
		for (excluded = graft->excluded; excluded; excluded = excluded->next) {
			uri = excluded->data;
			escaped_uri = gnome_vfs_escape_host_and_path_string (uri);
			path = gnome_vfs_get_local_path_from_uri (escaped_uri);
			g_free (escaped_uri);

			g_hash_table_insert (excludedH, path, GINT_TO_POINTER (1));
		}

		/* get the current and futures paths */
		uri = graft->uri;
		escaped_uri = gnome_vfs_escape_host_and_path_string (uri);
		path = gnome_vfs_get_local_path_from_uri (escaped_uri);
		g_free (escaped_uri);

		graft_path = graft->path;

		if (g_file_test (path, G_FILE_TEST_IS_DIR))
			result = brasero_burn_sum_explore_directory (sum,
								     path,
								     graft_path,
								     excludedH,
								     error);
		else
			result = brasero_burn_sum_start_md5 (sum,
							     path,
							     graft_path,
							     error);

		g_free (path);

		/* clean excluded hash table of all graft point excluded */
		g_hash_table_foreach_remove (excludedH,
					     brasero_burn_sum_clean_excluded_table_cb,
					     NULL);

		if (result != BRASERO_BURN_OK)
			break;
	}

	g_hash_table_destroy (excludedH);

	/* that's finished we close the file */
	fclose (sum->priv->file);
	sum->priv->file = NULL;

	return result;
}

struct _BraseroBurnSumThreadCtx {
	BraseroBurnSum *sum;
	BraseroBurnResult result;
	GError *error;
};
typedef struct _BraseroBurnSumThreadCtx BraseroBurnSumThreadCtx;

static gboolean
brasero_burn_sum_end (gpointer data)
{
	GError *error;
	BraseroBurnSum *sum;
	BraseroBurnSumThreadCtx *ctx;

	ctx = data;
	sum = ctx->sum;
	error = ctx->error;

	if (ctx->result == BRASERO_BURN_NOT_READY) {
		BRASERO_JOB_NOT_READY (sum);
	}
	else if (ctx->result == BRASERO_BURN_NOT_SUPPORTED) {
		BRASERO_JOB_NOT_SUPPORTED (sum);
	}
	else if (ctx->result == BRASERO_BURN_ERR)		
		brasero_job_error (BRASERO_JOB (sum), error);
	else
		brasero_job_finished (BRASERO_JOB (sum));

	sum->priv->end_id = 0;
	return FALSE;
}

static void
brasero_burn_sum_destroy (gpointer data)
{
	BraseroBurnSumThreadCtx *ctx;

	ctx = data;
	if (ctx->error)
		g_error_free (ctx->error);
	g_free (ctx);
}

static gpointer
brasero_burn_sum_thread (gpointer data)
{
	GError *error = NULL;
	BraseroBurnSum *sum;
	BraseroBurnResult result;
	BraseroBurnSumThreadCtx *ctx;

	sum = BRASERO_BURN_SUM (data);

	brasero_job_task_set_action (BRASERO_JOB (sum),
				    BRASERO_BURN_ACTION_CHECKSUM,
				    TRUE);

	result = brasero_burn_sum_explore_grafts (sum, &error);

	if (result == BRASERO_BURN_CANCEL)
		return NULL;

	ctx = g_new0 (BraseroBurnSumThreadCtx, 1);
	ctx->sum = sum;
	ctx->error = error;
	ctx->result = result;
	sum->priv->end_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
					     brasero_burn_sum_end,
					     ctx,
					     brasero_burn_sum_destroy);

	return NULL;
}

static BraseroBurnResult
brasero_burn_sum_start (BraseroJob *job,
			int in_fd,
			int *out_fd,
			GError **error)
{
	BraseroBurnSum *sum;

	sum = BRASERO_BURN_SUM (job);
	if (in_fd > 0 || out_fd)
		BRASERO_JOB_NOT_SUPPORTED (sum);

	if (!sum->priv->source)
		BRASERO_JOB_NOT_READY (sum);

	/* we start a thread for the exploration of the graft points */
	sum->priv->thread = g_thread_create (brasero_burn_sum_thread,
					     sum,
					     TRUE,
					     error);

	if (!sum->priv->thread)
		return BRASERO_BURN_ERR;

	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_burn_sum_stop (BraseroJob *job,
		       BraseroBurnResult retval)
{
	BraseroBurnSum *sum;

	sum = BRASERO_BURN_SUM (job);

	if (retval == BRASERO_BURN_CANCEL)
		g_remove (sum->priv->sums_path);

	if (sum->priv->thread) {
		sum->priv->cancel = 1;
		g_thread_join (sum->priv->thread);
		sum->priv->cancel = 0;
		sum->priv->thread = NULL;
	}

	if (sum->priv->file) {
		fclose (sum->priv->file);
		sum->priv->file = NULL;
	}

	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_burn_sum_get_written (BraseroJob *job,
			      gint64 *written)
{
	BraseroBurnSum *sum;

	sum = BRASERO_BURN_SUM (job);

	*written = sum->priv->written_b;
	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_burn_sum_get_action_string (BraseroJob *job,
				    BraseroBurnAction action,
				    gchar **string)
{
	*string = g_strdup (brasero_burn_action_to_string (action));

	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_burn_sum_set_source (BraseroJob *job,
			     const BraseroTrackSource *source,
			     GError **error)
{
	BraseroBurnSum *self;

	if (source->type != BRASERO_TRACK_SOURCE_DATA)
		BRASERO_JOB_NOT_SUPPORTED (job);

	self = BRASERO_BURN_SUM (job);

	if (self->priv->source)
		brasero_track_source_free (self->priv->source);

	self->priv->source = brasero_track_source_copy (source);
	self->priv->sums_ready = 0;

	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_burn_sum_set_output (BraseroImager *imager,
			     const gchar *output,
			     gboolean overwrite,
			     gboolean clean,
			     GError **error)
{
	BraseroBurnSum *sum;

	sum = BRASERO_BURN_SUM (imager);

	if (sum->priv->sums_path) {
		if (sum->priv->clean)
			g_remove (sum->priv->sums_path);

		g_free (sum->priv->sums_path);
		sum->priv->sums_path = NULL;
	}

	if (output)
		sum->priv->sums_path = g_strdup (output);

	sum->priv->sums_ready = 0;
	sum->priv->clean = (clean == TRUE);
	sum->priv->overwrite = (overwrite == TRUE);

	return BRASERO_BURN_OK;
}
    
static BraseroBurnResult
brasero_burn_sum_set_output_type (BraseroImager *imager,
				  BraseroTrackSourceType type,
				  BraseroImageFormat format,
				  GError **error)
{
	if (type != BRASERO_TRACK_SOURCE_DATA
	&&  type != BRASERO_TRACK_SOURCE_DEFAULT)
		BRASERO_JOB_NOT_SUPPORTED (imager);

	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_burn_sum_get_track (BraseroImager *imager,
			    BraseroTrackSource **source,
			    GError **error)
{
	BraseroBurnSum *self;
	BraseroGraftPt *graft;
	BraseroTrackSource *track;

	self = BRASERO_BURN_SUM (imager);

	if (!self->priv->sums_ready) {
		BraseroBurnResult result;

		result = brasero_job_run (BRASERO_JOB (self), error);
		if (result != BRASERO_BURN_OK)
			return result;
	}

	self->priv->sums_ready = 1;

	track = brasero_track_source_copy (self->priv->source);
	graft = g_new0 (BraseroGraftPt, 1);
	graft->uri = g_strconcat ("file://", self->priv->sums_path, NULL);
	graft->path = g_strdup ("/"BRASERO_CHECKSUM_FILE);
	track->contents.data.grafts = g_slist_prepend (track->contents.data.grafts, graft);

	*source = track;

	return BRASERO_BURN_OK;
}
