eog/libeog/eog-job.c
Claudio Saavedra 52a1664049 Add image_lock/unlock methods to ensure that there are no race conditions
2006-07-19  Claudio Saavedra  <csaavedra@alumnos.utalca.cl>

        * libeog/eog-image-private.h:
        * libeog/eog-image.c: (eog_image_dispose), (eog_image_init),
        (eog_image_lock), (eog_image_unlock), (eog_image_cancel_load):
        * libeog/eog-image.h:
        * libeog/eog-job-manager.c: (thread_start_func):
        * libeog/eog-job.c: (eog_job_call_action):
        * shell/eog-window.c: (eog_window_init), (job_image_load_action),
        (job_image_load_finished), (handle_image_selection_changed):

        Add image_lock/unlock methods to ensure that there are no
        race conditions when loading and unloading images (Fixes bug
        #340827). Patch from Callum McKenzie <callum@spooky-possum.org>.
2006-07-24 19:05:14 +00:00

453 lines
9.6 KiB
C

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <libgnome/gnome-macros.h>
#include "eog-job.h"
static guint last_job_id = 0;
#define DEBUG_EOG_JOB 0
enum {
PROP_0,
PROP_PROGRESS_THRESHOLD,
PROP_PROGRESS_N_PARTS,
PROP_PRIORITY
};
struct _EogJobPrivate {
GMutex *mutex;
guint id;
gpointer data;
EogJobStatus status;
GError *error;
float progress;
float progress_last_called;
guint progress_idle_id;
guint progress_nth_part;
guint progress_n_parts;
float progress_threshold;
guint priority;
EogJobActionFunc af;
EogJobCancelFunc cf;
EogJobFinishedFunc ff;
EogJobProgressFunc pf;
EogJobFreeDataFunc df;
};
static void
eog_job_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
EogJob *job;
g_return_if_fail (EOG_IS_JOB (object));
job = EOG_JOB (object);
switch (property_id) {
case PROP_PROGRESS_THRESHOLD:
job->priv->progress_threshold =
g_value_get_float (value);
break;
case PROP_PROGRESS_N_PARTS:
job->priv->progress_n_parts =
g_value_get_uint (value);
break;
case PROP_PRIORITY:
job->priv->priority =
g_value_get_uint (value);
break;
default:
g_assert_not_reached ();
}
}
static void
eog_job_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
EogJob *job;
g_return_if_fail (EOG_IS_JOB (object));
job = EOG_JOB (object);
switch (property_id) {
case PROP_PROGRESS_THRESHOLD:
g_value_set_float (value,
job->priv->progress_threshold);
break;
case PROP_PROGRESS_N_PARTS:
g_value_set_uint (value,
job->priv->progress_n_parts);
break;
case PROP_PRIORITY:
g_value_set_uint (value,
job->priv->priority);
break;
default:
g_assert_not_reached ();
}
}
static void
eog_job_finalize (GObject *object)
{
EogJob *instance = EOG_JOB (object);
g_free (instance->priv);
instance->priv = NULL;
}
static void
eog_job_dispose (GObject *object)
{
EogJobPrivate *priv;
priv = EOG_JOB (object)->priv;
#if DEBUG_EOG_JOB
g_print ("Job %.3u: disposing ...\n", priv->id);
#endif
if (priv->mutex != NULL) {
g_mutex_lock (priv->mutex);
if (priv->data != NULL)
{
if (priv->df != NULL) {
(*priv->df) (priv->data);
}
else if (G_IS_OBJECT (priv->data))
g_object_unref (G_OBJECT (priv->data));
priv->data = NULL;
}
g_mutex_unlock (priv->mutex);
g_mutex_free (priv->mutex);
priv->mutex = NULL;
}
#if DEBUG_EOG_JOB
g_print ("Job %.3u: disposing end\n", priv->id);
#endif
}
static void
eog_job_init (EogJob *obj)
{
EogJobPrivate *priv;
guint id;
id = ++last_job_id;
if (id == 0) { /* in overflow case */
++last_job_id;
++id;
}
#if DEBUG_EOG_JOB
g_print ("Instantiate job with id %u.\n", id);
#endif
priv = g_new0 (EogJobPrivate, 1);
priv->id = id;
priv->mutex = g_mutex_new ();
priv->error = NULL;
priv->data = NULL;
priv->af = NULL;
priv->cf = NULL;
priv->ff = NULL;
priv->pf = NULL;
priv->df = NULL;
priv->progress = 0.0;
priv->progress_idle_id = 0;
priv->progress_threshold = 0.1;
priv->progress_n_parts = 1;
priv->priority = EOG_JOB_PRIORITY_NORMAL;
obj->priv = priv;
}
static void
eog_job_class_init (EogJobClass *klass)
{
GObjectClass *object_class = (GObjectClass*) klass;
object_class->finalize = eog_job_finalize;
object_class->dispose = eog_job_dispose;
object_class->set_property = eog_job_set_property;
object_class->get_property = eog_job_get_property;
/* Properties */
g_object_class_install_property (
object_class,
PROP_PROGRESS_THRESHOLD,
g_param_spec_float ("progress-threshold", NULL,
"Difference threshold between two progress values"
"before calling progress callback function again",
0.0, 1.0, 0.1,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_PROGRESS_N_PARTS,
g_param_spec_uint ("progress-n-parts", NULL,
"Number of parts for this progress so that "
"the total progress is the sum of progress "
"for each part devided through number of parts.",
0, G_MAXINT, 1,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_PRIORITY,
g_param_spec_uint ("priority", NULL,
"Priority of the job.",
0, G_MAXINT, 1,
G_PARAM_WRITABLE));
}
G_DEFINE_TYPE (EogJob, eog_job, G_TYPE_OBJECT)
EogJob*
eog_job_new (GObject *data_obj,
EogJobActionFunc af,
EogJobFinishedFunc ff,
EogJobCancelFunc cf,
EogJobProgressFunc pf)
{
g_return_val_if_fail (G_IS_OBJECT (data_obj), NULL);
return eog_job_new_full ((gpointer) data_obj, af, ff, cf, pf, NULL);
}
/* called from main thread */
EogJob*
eog_job_new_full (gpointer data,
EogJobActionFunc af,
EogJobFinishedFunc ff,
EogJobCancelFunc cf,
EogJobProgressFunc pf,
EogJobFreeDataFunc df)
{
EogJob *job;
EogJobPrivate *priv;
g_return_val_if_fail (af != NULL, NULL);
job = g_object_new (EOG_TYPE_JOB, NULL);
priv = job->priv;
if (G_IS_OBJECT (data)) {
g_object_ref (G_OBJECT (data));
}
priv->data = data;
priv->status = EOG_JOB_STATUS_WAITING;
priv->af = af;
priv->ff = ff;
priv->cf = cf;
priv->pf = pf;
priv->df = df;
return job;
}
/* called from main thread */
EogJobStatus
eog_job_get_status (EogJob *job)
{
g_return_val_if_fail (EOG_IS_JOB (job), EOG_JOB_STATUS_ERROR);
return job->priv->status;
}
/* called from main thread */
guint
eog_job_get_id (EogJob *job)
{
g_return_val_if_fail (EOG_IS_JOB (job), 0);
return job->priv->id;
}
/* called from main thread */
gboolean
eog_job_get_success (EogJob *job)
{
gboolean success;
g_return_val_if_fail (EOG_IS_JOB (job), FALSE);
g_mutex_lock (job->priv->mutex);
success = ((job->priv->status == EOG_JOB_STATUS_FINISHED) &&
(job->priv->error == NULL));
g_mutex_unlock (job->priv->mutex);
return success;
}
void
eog_job_part_finished (EogJob *job)
{
g_return_if_fail (EOG_IS_JOB (job));
job->priv->progress_nth_part =
MIN ((job->priv->progress_nth_part + 1),
job->priv->progress_n_parts);
}
/* private func, called within main thread from idle loop */
static gboolean
eog_job_call_progress (gpointer data)
{
EogJob *job = EOG_JOB (data);
g_assert (job->priv->pf != NULL);
g_mutex_lock (job->priv->mutex);
job->priv->progress_idle_id = 0;
g_mutex_unlock (job->priv->mutex);
/* call progress callback */
(*job->priv->pf) (job, job->priv->data, job->priv->progress);
return FALSE;
}
/* called from concurrent thread */
void
eog_job_set_progress (EogJob *job, float progress)
{
EogJobPrivate *priv;
g_return_if_fail (EOG_IS_JOB (job));
priv = job->priv;
g_mutex_lock (job->priv->mutex);
/* calculate new progress */
priv->progress = ((float) priv->progress_nth_part + progress) /
priv->progress_n_parts;
/* check if all preconditions are met for calling progress callback */
if ((priv->pf != NULL) &&
(priv->progress_idle_id == 0) &&
(priv->progress == 1.0 ||
(priv->progress - priv->progress_last_called) >= priv->progress_threshold))
{
priv->progress_last_called = priv->progress;
priv->progress_idle_id = g_idle_add (eog_job_call_progress, job);
}
g_mutex_unlock (job->priv->mutex);
}
/* this runs in its own thread
* called by EogJobManager
*/
void
eog_job_call_action (EogJob *job)
{
gboolean do_action = TRUE;
g_return_if_fail (EOG_IS_JOB (job));
g_mutex_lock (job->priv->mutex);
if (job->priv->status != EOG_JOB_STATUS_WAITING)
do_action = FALSE;
if (job->priv->af == NULL) {
job->priv->status = EOG_JOB_STATUS_ERROR;
do_action = FALSE;
}
if (do_action) {
job->priv->status = EOG_JOB_STATUS_RUNNING;
job->priv->progress = 0.0;
job->priv->progress_last_called = 0.0;
job->priv->progress_nth_part = 0;
}
g_mutex_unlock (job->priv->mutex);
if (!do_action) {
return;
}
/* do the actual work */
(*job->priv->af) (job, job->priv->data, &job->priv->error);
g_mutex_lock (job->priv->mutex);
if (job->priv->status != EOG_JOB_STATUS_CANCELED)
job->priv->status = EOG_JOB_STATUS_FINISHED;
g_mutex_unlock (job->priv->mutex);
}
/* called within main thread from idle loop
* by EogJobManager
*/
gboolean
eog_job_call_canceled (EogJob *job)
{
gboolean do_cancel = FALSE;
gboolean valid_state = FALSE;
g_return_val_if_fail (EOG_IS_JOB (job), FALSE);
g_mutex_lock (job->priv->mutex);
valid_state = (job->priv->status == EOG_JOB_STATUS_WAITING ||
job->priv->status == EOG_JOB_STATUS_RUNNING);
do_cancel = (job->priv->cf != NULL && valid_state);
if (valid_state) {
job->priv->status = EOG_JOB_STATUS_CANCELED;
}
g_mutex_unlock (job->priv->mutex);
if (do_cancel) {
/* call cancel function */
(*job->priv->cf) (job, job->priv->data);
}
return valid_state;
}
/* called within main thread from idle loop
* by EogJobManager
*/
void
eog_job_call_finished (EogJob *job)
{
gboolean call_finished = FALSE;
g_return_if_fail (EOG_IS_JOB (job));
g_mutex_lock (job->priv->mutex);
g_assert (job->priv->status == EOG_JOB_STATUS_FINISHED ||
job->priv->status == EOG_JOB_STATUS_CANCELED);
call_finished = (job->priv->ff != NULL);
g_mutex_unlock (job->priv->mutex);
if (call_finished) {
/* call finished callback */
(*job->priv->ff) (job, job->priv->data, job->priv->error);
}
}
EogJobPriority
eog_job_get_priority (EogJob *job)
{
g_return_val_if_fail (EOG_IS_JOB (job), EOG_JOB_PRIORITY_NORMAL);
return job->priv->priority;
}