/***************************************************************************
 *            job.c
 *
 *  dim jan 22 10:40:26 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.
 */


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

#include <string.h>

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

#include "burn-job.h"
#include "burn-basics.h"
#include "burn-session.h"
#include "brasero-marshal.h"
 
static void brasero_job_class_init (BraseroJobClass *klass);
static void brasero_job_init (BraseroJob *sp);
static void brasero_job_finalize (GObject *object);


static BraseroBurnResult
brasero_job_stop (BraseroJob *job,
		  BraseroBurnResult retval,
		  GError *error);

struct _BraseroJobTask {
	/* The loop for the task */
	GMainLoop *loop;

	/* used to poll for progress (every 0.5 sec) */
	gint progress_report_id;
	gdouble progress;

	/* keep track of time */
	GTimer *timer;
	gint64 b_written;

	/* used for immediate rate */
	gint64 last_written;
	gdouble last_elapsed;

	/* the current action */
	BraseroBurnAction action;

	/* result of the task */
	BraseroBurnResult retval;
	GError *error;

	gint action_changed:1;
};
typedef struct _BraseroJobTask BraseroJobTask;

#define TASK_KEY "TASK_KEY"

struct BraseroJobPrivate {
	BraseroBurnSession *session;

	BraseroJob *master;
	BraseroJob *slave;

	int relay_slave_signal:1;
	int run_slave:1;

	int is_running:1;
	int dangerous:1;
	int debug:1;
};

typedef enum {
	ERROR_SIGNAL,
	ACTION_CHANGED_SIGNAL,
	PROGRESS_CHANGED_SIGNAL,
	ANIMATION_CHANGED_SIGNAL,
	LAST_SIGNAL
} BraseroJobSignalType;

static guint brasero_job_signals [LAST_SIGNAL] = { 0 };
static GObjectClass *parent_class = NULL;

GType
brasero_job_get_type ()
{
	static GType type = 0;

	if(type == 0) {
		static const GTypeInfo our_info = {
			sizeof (BraseroJobClass),
			NULL,
			NULL,
			(GClassInitFunc)brasero_job_class_init,
			NULL,
			NULL,
			sizeof (BraseroJob),
			0,
			(GInstanceInitFunc)brasero_job_init,
		};

		type = g_type_register_static(G_TYPE_OBJECT, 
			"BraseroJob", &our_info, 0);
	}

	return type;
}

static void
brasero_job_class_init (BraseroJobClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	parent_class = g_type_class_peek_parent(klass);
	object_class->finalize = brasero_job_finalize;
	
	brasero_job_signals[PROGRESS_CHANGED_SIGNAL] =
	    g_signal_new ("progress_changed",
			  G_TYPE_FROM_CLASS (klass),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (BraseroJobClass,
					   progress_changed),
			  NULL, NULL,
			  g_cclosure_marshal_VOID__VOID,
			  G_TYPE_NONE,
			  0);
	brasero_job_signals[ACTION_CHANGED_SIGNAL] =
	    g_signal_new ("action_changed",
			  G_TYPE_FROM_CLASS (klass),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (BraseroJobClass,
					   action_changed),
			  NULL, NULL,
			  g_cclosure_marshal_VOID__INT,
			  G_TYPE_NONE,
			  1,
			  G_TYPE_INT);
	brasero_job_signals[ANIMATION_CHANGED_SIGNAL] =
	    g_signal_new ("animation_changed",
			  G_TYPE_FROM_CLASS (klass),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (BraseroJobClass,
					   animation_changed),
			  NULL, NULL,
			  g_cclosure_marshal_VOID__BOOLEAN,
			  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
	brasero_job_signals[ERROR_SIGNAL] =
	    g_signal_new ("error",
			  G_TYPE_FROM_CLASS (klass),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (BraseroJobClass,
					   error),
			  NULL, NULL,
			  brasero_marshal_INT__INT,
			  G_TYPE_INT, 1, G_TYPE_INT);
}

static void
brasero_job_init (BraseroJob *obj)
{
	obj->priv = g_new0 (BraseroJobPrivate, 1);
	obj->priv->relay_slave_signal = 1;
}

static void
brasero_job_finalize (GObject *object)
{
	BraseroJob *cobj;
	BraseroJobTask *task;

	cobj = BRASERO_JOB (object);

	task = g_object_get_data (G_OBJECT (cobj), TASK_KEY);

	if (task)
		brasero_job_stop (cobj, BRASERO_BURN_CANCEL, NULL);

	/* NOTE: it can't reach this function and have a
	 * master since the master holds a reference on it */
	if (cobj->priv->slave) {
		cobj->priv->slave->priv->master = NULL;
		g_object_unref (cobj->priv->slave);
		cobj->priv->slave = NULL;
	}

	if (cobj->priv->session) {
		g_object_unref (cobj->priv->session);
		cobj->priv->session = NULL;
	}

	g_free (cobj->priv);
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

void
brasero_job_set_dangerous (BraseroJob *job, gboolean value)
{
	job->priv->dangerous = value;
}

void
brasero_job_set_debug (BraseroJob *job, gboolean value)
{
	job->priv->debug = value;
}

void
brasero_job_set_session (BraseroJob *job, BraseroBurnSession *session)
{
	if (job->priv->session)
		g_object_unref (job->priv->session);

	g_object_ref (session);
	job->priv->session = session;
}

static BraseroJob *
brasero_job_get_emitter (BraseroJob *job)
{
	g_return_val_if_fail (job != NULL, NULL);

	if (!job->priv->master)
		return job;

	while (job->priv->master)
		job = job->priv->master;

	if (job->priv->relay_slave_signal)
		return job;

	return NULL;
}

static BraseroJobTask *
brasero_job_get_task (BraseroJob *job, BraseroJob **leader)
{
	BraseroJob *iter;
	BraseroJobTask *task;

	/* it could happen that a master has been asked about a task being
	 * performed by one of his slave so we check the slaves to see if 
	 * they are not the leaders of some task */
	iter = job;
	while (iter && (task = g_object_get_data (G_OBJECT (iter), TASK_KEY)) == NULL) {
		/* see if this parent accepts to relay to its children */
		if (!iter->priv->relay_slave_signal)
			return NULL;

		iter = iter->priv->slave;
	}

	if (leader)
		*leader = iter;

	return task;
}

static gboolean
brasero_job_report_progress_cb (gpointer data)
{
	BraseroJob *job = BRASERO_JOB (data);
	BraseroJobTask *task = NULL;
	BraseroJob *iter;

	task = g_object_get_data (G_OBJECT (job), TASK_KEY);
	if (!task)
		return FALSE;

	/* some jobs need to be called periodically to update
	 * their status because the main process run in a thread */
	for (iter = job; iter; iter = iter->priv->slave) {
		BraseroJobClass *klass;

		klass = BRASERO_JOB_GET_CLASS (iter);
		if (klass->check_status)
			klass->check_status (iter);
	}

	/* emit the signals if need be */
	job = brasero_job_get_emitter (job);
	if (!job)
		return TRUE;

	if (task->action_changed) {
		g_signal_emit (job,
			       brasero_job_signals [ACTION_CHANGED_SIGNAL],
			       0,
			       task->action);

		task->action_changed = 0;
	}

	if (!task->timer)
		return TRUE;

	g_signal_emit (job,
		       brasero_job_signals [PROGRESS_CHANGED_SIGNAL],
		       0);

	return TRUE;
}

static BraseroBurnResult
brasero_job_loop (BraseroJob *job, GError **error)
{
	BraseroJobTask task = { 	NULL,
					0,
					-1.0,
					NULL,
					0,
					0,
					0,
					BRASERO_BURN_ACTION_NONE,
					BRASERO_BURN_OK, NULL };
	BraseroJob *emiter;

	/* start the loop 
	 * NOTE: loop is unreffed in brasero_job_stop */
	BRASERO_JOB_LOG (job, "in loop");

	g_object_set_data (G_OBJECT (job), TASK_KEY, &task);
	task.loop = g_main_loop_new (NULL, FALSE);

	/* we try to update the progress every 0.5 sec since all went well */
	emiter = brasero_job_get_emitter (job);
	if (emiter) {
		g_signal_emit (emiter,
			       brasero_job_signals [PROGRESS_CHANGED_SIGNAL],
			       0);
	}

	task.progress_report_id = g_timeout_add (500,
						 brasero_job_report_progress_cb,
						 job);

	g_main_loop_run (task.loop);

	/* stop all progress reporting thing */
	if (task.progress_report_id) {
		g_source_remove (task.progress_report_id);
		task.progress_report_id = 0;
	}

	if (task.timer) {
		g_timer_destroy (task.timer);
		task.timer = NULL;
	}

	if (task.error) {
		g_propagate_error (error, task.error);
		task.error = NULL;
	}

	return task.retval;	
}

static BraseroBurnResult
brasero_job_plug (BraseroJob *job,
		  BraseroJob *prev_job,
		  int *in_fd,
		  int *out_fd,
		  GError **error)
{
	BraseroBurnResult result;
	BraseroJobClass *klass;

	klass = BRASERO_JOB_GET_CLASS (job);
	if (!klass->start) {
		BRASERO_JOB_LOG (job, "no start method");
		BRASERO_JOB_NOT_SUPPORTED (job);
	}

	result = klass->start (job,
			       *in_fd,
			       out_fd,
			       error);

	if (result != BRASERO_BURN_OK)
		return result;

	if (out_fd && *out_fd == -1) {
		BRASERO_JOB_LOG (job, "can't be plugged");
		BRASERO_JOB_NOT_SUPPORTED (job);
	}

	job->priv->is_running = 1;

	if (out_fd && *out_fd != -1) {
		*in_fd = *out_fd;
		*out_fd = -1;
	}

	return BRASERO_BURN_OK;
}

static BraseroBurnResult
brasero_job_send_stop_signal (BraseroJob *job,
			      BraseroBurnResult retval)
{
	BraseroJob *iter;
	BraseroJob *emiter;
	BraseroJobClass *klass;
	BraseroBurnResult result = retval;

	/* we stop all the first slave first and then go up the list.
	 * That's why we reverse the list here. */
	iter = job;
	while (iter->priv->slave && iter->priv->slave->priv->is_running)
		iter = iter->priv->slave;

	do {
		/* stop task for real now */
		klass = BRASERO_JOB_GET_CLASS (iter);

		BRASERO_JOB_LOG (job, "stopping");
		if (klass->stop)
			result = klass->stop (iter, result);
		BRASERO_JOB_LOG (job, "stopped");

		iter->priv->is_running = 0;
		iter = iter->priv->master;
	} while (iter && iter != job->priv->master);

	/* report progress for the last time, job being the leader for the task */
	emiter = brasero_job_get_emitter (job);
	if (emiter) {
		g_signal_emit (emiter,
			       brasero_job_signals [ACTION_CHANGED_SIGNAL],
			       0,
			       BRASERO_BURN_ACTION_NONE);

		g_signal_emit (emiter,
			       brasero_job_signals [PROGRESS_CHANGED_SIGNAL],
			       0);
	}

	/* we don't want to lose the original result if it was not OK */
	if (retval != BRASERO_BURN_OK)
		return retval;

	return result;
}

static BraseroBurnResult
brasero_job_pre_init (BraseroJob *job,
		      gboolean has_master_running,
		      GError **error)
{
	BraseroJobClass *klass;
	BraseroBurnResult result = BRASERO_BURN_OK;

	klass = BRASERO_JOB_GET_CLASS (job);

	if (!BRASERO_IS_JOB (job)) {
		BRASERO_JOB_LOG (job, "not a job");
		return BRASERO_BURN_ERR;
	}

	if (g_object_get_data (G_OBJECT (job), TASK_KEY)) {
		BRASERO_JOB_LOG (job, "running");
		return BRASERO_BURN_RUNNING;
	}

	if (klass->start_init)
		result = klass->start_init (job,
					    has_master_running,
					    error);

	return result;
}

BraseroBurnResult
brasero_job_run (BraseroJob *job, GError **error)
{
	BraseroBurnResult result = BRASERO_BURN_OK;
	BraseroJob *prev_job = NULL;
	BraseroJob *iter;
	int out_fd = -1;
	int in_fd = -1;

	/* check the job or one of its master is not running */
	if (brasero_job_is_running (job)) {
		BRASERO_JOB_LOG (job, "is already running");
		return BRASERO_BURN_RUNNING;
	}

	/* we first init all jobs starting from the master down to the slave */
	iter = job;
	while (1) {
		result = brasero_job_pre_init (iter, (iter != job), error);
		if (result != BRASERO_BURN_OK)
			goto error;

		if (!iter->priv->run_slave)
			break;

		if (!iter->priv->slave)
			break;

		iter = iter->priv->slave;
	}	

	/* now start from the slave up to the master */
	prev_job = NULL;
	while (iter != job) {
		result = brasero_job_plug (iter,
					   prev_job,
					   &in_fd,
					   &out_fd,
					   error);

		if (result != BRASERO_BURN_OK)
			goto error;
	
		prev_job = iter;
		iter = iter->priv->master;
	}

	result = brasero_job_plug (iter,
				   prev_job,
				   &in_fd,
				   NULL,
				   error);

	if (result != BRASERO_BURN_OK)
		goto error;

	result = brasero_job_loop (iter, error);
	return result;

error:
	brasero_job_send_stop_signal (job, result);
	return result;
}

gboolean
brasero_job_is_running (BraseroJob *job)
{
	g_return_val_if_fail (job != NULL, FALSE);

	if (job->priv->is_running)
		return TRUE;

	return FALSE;
}

static BraseroBurnResult
brasero_job_stop (BraseroJob *job,
		  BraseroBurnResult retval,
		  GError *error)
{
	BraseroBurnResult result = BRASERO_BURN_OK;
	BraseroJobTask *task = NULL;
	BraseroJob *job_owning_task;
	BraseroJob *iter;
	GMainLoop *loop;

	if (error) {
		BRASERO_JOB_LOG (job,
				 "asked to stop\n"
				 "\tstatus\t= %i\n"
				 "\terror\t\t= %i\n"
				 "\tmessage\t= \"%s\"",
				 retval,
				 error->code,
				 error->message);
	}
	else
		BRASERO_JOB_LOG (job,
				 "asked to stop\n"
				 "\tstatus\t= %i",
				 retval);

	if (job->priv->is_running) {
		task = g_object_get_data (G_OBJECT (job), TASK_KEY);
		job_owning_task = job;
	}
	else {
		/* maybe we signalled a master to stop and it isn't running
		 * while his slaves are: see if it has slaves running */
		iter = job->priv->slave;
		while (iter && !(task = g_object_get_data (G_OBJECT (iter), TASK_KEY)))
			iter = iter->priv->slave;

		job_owning_task = iter;
	}

	if (!task) {
		/* if we reached this point it means that the job itself nor its slaves
		 * is controlling the running */

		/* if this job hasn't got the running bit no need to see if its master's
		 * running for fear we might stop the master unwillingly */
		if (!job->priv->is_running) {
			BRASERO_JOB_LOG (job, "the job isn't running (nor his slaves)");
			return BRASERO_BURN_NOT_RUNNING;
		}

		/* search the master which is running */
		iter = job->priv->master;
		while (iter && !(task = g_object_get_data (G_OBJECT (iter), TASK_KEY)))
			iter = iter->priv->master;

		if (!task) {
			BRASERO_JOB_LOG (job, "isn't running (nor his slaves)");
			return BRASERO_BURN_NOT_RUNNING;
		}

		job_owning_task = iter;

		/* we discard all messages from slaves saying that all is good */
		if (retval == BRASERO_BURN_OK) {
			if (task->loop) {
				BRASERO_JOB_LOG (job, "is not a leader");
				return BRASERO_BURN_ERR;
			}

			return BRASERO_BURN_OK;
		}
	}

	/* means we still accept errors. This is meant for
	 * master to override the errors of their slaves */
	if (!task->loop) {
		if (error) {
			if (task->error)
				g_error_free (task->error);

			task->retval = retval;
			task->error = error;
		}

		return BRASERO_BURN_OK;
	}

	loop = task->loop;
	task->loop = NULL;

	if (retval == BRASERO_BURN_OK) {
		task->progress = 1.0;
		task->b_written = -1;
	}

	/* tell all the jobs we've stopped */
	result = brasero_job_send_stop_signal (job_owning_task, retval);

	/* stop the loop */
	g_object_set_data (G_OBJECT (job), TASK_KEY, NULL);

	if (job_owning_task == job) {
		if (task->error)
			g_error_free (task->error);

		task->error = error;
		task->retval = result;
	}
	else if (!task->error) {
		task->error = error;
		task->retval = retval;
	}

	g_main_loop_quit (loop);
	g_main_loop_unref (loop);

	BRASERO_JOB_LOG (job, "got out of loop");
	return result;
}

BraseroBurnResult
brasero_job_cancel (BraseroJob *job, gboolean protect)
{
	g_return_val_if_fail (job != NULL, BRASERO_BURN_ERR);

	if (protect && job->priv->dangerous)
		return BRASERO_BURN_DANGEROUS;

	return brasero_job_stop (job, BRASERO_BURN_CANCEL, NULL);
}

/* used for implementation */
BraseroBurnResult
brasero_job_finished (BraseroJob *job)
{
	return brasero_job_stop (job, BRASERO_BURN_OK, NULL);
}

BraseroBurnResult
brasero_job_error (BraseroJob *job, GError *error)
{
	BraseroBurnResult result = BRASERO_BURN_ERR;

	/* There was an error: signal it. That's mainly done
	 * for BraseroBurnCaps to override the result value */
	g_signal_emit (job,
		       brasero_job_signals [ERROR_SIGNAL],
		       0,
		       error->code,
		       &result);

	return brasero_job_stop (job, result, error);
}

/******************************** job tasks ************************************/
BraseroBurnResult
brasero_job_task_start_progress (BraseroJob *job)
{
	BraseroJobTask *task = NULL;

	/* to set anything about the task the job must be the
	 * leader of the task */
	task = g_object_get_data (G_OBJECT (job), TASK_KEY);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	if (!task->timer)
		task->timer = g_timer_new ();
	else
		g_timer_start (task->timer);

	brasero_job_task_get_written (job, &task->b_written);
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_set_progress (BraseroJob *job, gdouble progress)
{
	BraseroJobTask *task = NULL;

	/* to set anything about the task the job must be the
	 * leader of the task */
	task = g_object_get_data (G_OBJECT (job), TASK_KEY);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	task->progress = progress;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_get_progress (BraseroJob *job, gdouble *progress)
{
	BraseroJobTask *task = NULL;

	task = brasero_job_get_task (job, NULL);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	if (task->progress < 0.0)
		return BRASERO_BURN_NOT_READY;
	
	*progress = task->progress;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_set_action (BraseroJob *job,
			     BraseroBurnAction action,
			     gboolean force)
{
	BraseroJobTask *task = NULL;

	/* to emit an action changed signal, the job must be the
	 * task leader and it's master must allow signal relaying */
	task = g_object_get_data (G_OBJECT (job), TASK_KEY);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	if (!force && task->action == action)
		return BRASERO_BURN_OK;

	task->action = action;
	task->action_changed = 1;

	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_get_action (BraseroJob *job,
				BraseroBurnAction *action)
{
	BraseroJobTask *task = NULL;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);

	/* to emit an action changed signal, the job must be the
	 * task leader and it's master must allow signal relaying */
	task = brasero_job_get_task (job, NULL);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	*action = task->action;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_get_elapsed (BraseroJob *job, gdouble *elapsed)
{
	BraseroJobTask *task = NULL;

	task = brasero_job_get_task (job, NULL);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	if (!task->timer)
		return BRASERO_BURN_NOT_READY;

	if (elapsed)
		*elapsed = g_timer_elapsed (task->timer, NULL);

	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_get_rate (BraseroJob *job, gint64 *rate)
{
	gdouble elapsed;
	gint64 b_written;
	BraseroJobClass *klass;
	BraseroBurnResult result;
	BraseroJobTask *task = NULL;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);

	BRASERO_JOB_LOG (job, "get_rate");

	task = brasero_job_get_task (job, &job);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	klass = BRASERO_JOB_GET_CLASS (job);
	if (klass->get_rate) {
		if (!rate)
			return BRASERO_BURN_OK;

		return klass->get_rate (job, rate);
	}

	if (task->action != BRASERO_BURN_ACTION_WRITING
	&&  task->action != BRASERO_BURN_ACTION_CREATING_IMAGE
	&&  task->action != BRASERO_BURN_ACTION_DRIVE_COPY) {
		*rate = -1;
		return BRASERO_BURN_OK;
	}

	result = brasero_job_task_get_written (job, &b_written);
	if (result != BRASERO_BURN_OK)
		return result;

	if (!rate)
		return BRASERO_BURN_OK;

	if (!task->timer)
		return BRASERO_BURN_NOT_READY;

	elapsed = g_timer_elapsed (task->timer, NULL);
	if (elapsed != task->last_elapsed) {
		*rate = (gdouble) (b_written - task->last_written) /
			(gdouble) (elapsed - task->last_elapsed);
	
		task->last_written = b_written;
		task->last_elapsed = elapsed;
	}
	else
		*rate = 0;

	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_get_average_rate (BraseroJob *job, gint64 *rate)
{
	gint64 b_written;
	BraseroBurnResult result;
	BraseroJobTask *task = NULL;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);

	BRASERO_JOB_LOG (job, "get_average_rate");

	task = brasero_job_get_task (job, &job);
	if (!task)
		return BRASERO_BURN_NOT_RUNNING;

	result = brasero_job_task_get_written (job, &b_written);
	if (result != BRASERO_BURN_OK)
		return result;

	if (!rate)
		return BRASERO_BURN_OK;

	if (!task->timer)
		return BRASERO_BURN_NOT_READY;

	/* calculate average rate */
	*rate = ((gdouble) (b_written - task->b_written) /
			     g_timer_elapsed (task->timer, NULL));

	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_task_get_written (BraseroJob *job, gint64 *written)
{
	BraseroJobClass *klass;
	BraseroJob *leader;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);

	BRASERO_JOB_LOG (job, "get_written");

	/* we get the leader of the running task */
	if (!brasero_job_get_task (job, &leader))
		return BRASERO_BURN_NOT_RUNNING;

	klass = BRASERO_JOB_GET_CLASS (leader);

	if (!klass->get_written)
		BRASERO_JOB_NOT_SUPPORTED (leader);

	if (!written)
		return BRASERO_BURN_OK;

	return klass->get_written (leader, written);
}

/************************* for jobs only ***************************************/
BraseroBurnResult
brasero_job_get_action_string (BraseroJob *job,
			       BraseroBurnAction action,
			       gchar **string)
{
	const gchar *tmp;
	BraseroJobClass *klass;
	BraseroBurnResult result;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);
	g_return_val_if_fail (string != NULL, BRASERO_BURN_ERR);

	BRASERO_JOB_LOG (job, "get_action_string");

	klass = BRASERO_JOB_GET_CLASS (job);
	if (klass->get_action_string) {
		result = (* klass->get_action_string) (job,
						       action,
						       string);
		if (result == BRASERO_BURN_OK)
			return result;
	}

	tmp = brasero_burn_action_to_string (action);
	*string = g_strdup (tmp);

	return BRASERO_BURN_OK;
}

void
brasero_job_set_run_slave (BraseroJob *job, gboolean run_slave)
{
	g_return_if_fail (BRASERO_IS_JOB (job));
	job->priv->run_slave = (run_slave == TRUE);
}

void
brasero_job_set_relay_slave_signals (BraseroJob *job, gboolean relay)
{
	g_return_if_fail (BRASERO_IS_JOB (job));
	job->priv->relay_slave_signal = relay;
}

BraseroJob *
brasero_job_get_slave (BraseroJob *master)
{
	g_return_val_if_fail (BRASERO_IS_JOB (master), NULL);

	return master->priv->slave;
}

BraseroBurnResult
brasero_job_set_slave (BraseroJob *master, BraseroJob *slave)
{
	g_return_val_if_fail (BRASERO_IS_JOB (master), BRASERO_BURN_ERR);
	g_return_val_if_fail (master != slave, BRASERO_BURN_ERR);

	if (slave)
		g_return_val_if_fail (BRASERO_IS_JOB (slave), BRASERO_BURN_ERR);

	/* check if one of them is running */
	if (brasero_job_is_running (master)
	|| (slave && brasero_job_is_running (slave)))
		return BRASERO_BURN_RUNNING;

	/* set */
	if (master->priv->slave) {
		master->priv->slave->priv->master = NULL;
		g_object_unref (master->priv->slave);
	}

	master->priv->slave = slave;

	if (!slave)
		return BRASERO_BURN_OK;

	/* NOTE: the slave may already have a reference from a master,
	 * that's why we don't unref it to ref it just afterwards in 
	 * case its reference count reaches zero in between*/
	if (slave->priv->master)
		slave->priv->master->priv->slave = NULL;
	else
		g_object_ref (slave);

	slave->priv->master = master;
	return BRASERO_BURN_OK;
}

BraseroBurnResult
brasero_job_set_source (BraseroJob *job,
			const BraseroTrackSource *source,
			GError **error)
{
	BraseroJobClass *klass;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);

	BRASERO_JOB_LOG (job, "set_source");

	if (brasero_job_is_running (job))
		return BRASERO_BURN_RUNNING;

	klass = BRASERO_JOB_GET_CLASS (job);
	if (!klass->set_source)
		BRASERO_JOB_NOT_SUPPORTED (job);

	return (* klass->set_source) (job,
				       source,
				       error);
}

BraseroBurnResult
brasero_job_set_rate (BraseroJob *job, gint64 rate)
{
	BraseroJobClass *klass;

	g_return_val_if_fail (BRASERO_IS_JOB (job), BRASERO_BURN_ERR);

	klass = BRASERO_JOB_GET_CLASS (job);
	if (!klass->set_rate)
		BRASERO_JOB_NOT_SUPPORTED (job);

	BRASERO_JOB_LOG (job, "set_rate");

	return klass->set_rate (job, rate);
}

/* used for debugging */
void
brasero_job_log_message (BraseroJob *job,
			 const gchar *format,
			 ...)
{
	g_return_if_fail (job != NULL);
	g_return_if_fail (format != NULL);

	if (job->priv->session) {
		va_list arg_list;

		va_start (arg_list, format);
		brasero_burn_session_logv (job->priv->session, format, arg_list);
		va_end (arg_list);
	}

	if (job->priv->debug)
		BRASERO_BURN_LOGV (format);
}
