Logo Search packages:      
Sourcecode: eggcups version File versions  Download package

ec-cups-job-monitor.c

/*
 *  Copyright (C) 2004 Red Hat, Inc.
 *  Written by Colin Walters <walters@redhat.com>
 *
 *  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 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 "ec-cups-job-monitor.h"
#include "rb-debug.h"
#include "ec-marshal.h"
#include <libgnome/gnome-i18n.h>
#include <libgnomecups/gnome-cups-request.h>
#include <gdk/gdk.h>
#include <stdio.h>

#define TIMEOUT_SECONDS (40)

#define POLL_MSEC 5000

G_DEFINE_TYPE(ECCupsJobMonitor, ec_cups_job_monitor, G_TYPE_OBJECT)

00035 struct ECMonitoredJob
{
      guint32 id;
      char *path;
      GTimeVal last_update;
      guint async_query_id;
      GnomeCupsJob *job;
};

static void ec_cups_job_monitor_finalize (GObject *object);
static void ec_cups_job_monitor_dispose (GObject *object);

static gboolean poll_all_queues (ECCupsJobMonitor *mon);
static void poll_jobs (const char *printer_name, GList *jobs, ECCupsJobMonitor *mon);
static void poll_job (ECCupsJobMonitor *mon, const char *host, struct ECMonitoredJob *monjob);
static void lookup_job (ECCupsJobMonitor *mon, const char *host, guint32 id, GList **jobs, GList **link);
static void remove_job_internal (ECCupsJobMonitor *mon, const char *host, guint32 job_id);
static void remove_job_deferred (ECCupsJobMonitor *mon, const char *host, guint32 job_id);
static struct ECMonitoredJob * monitored_job_new (guint32 id, const char *path);
static void monitored_job_destroy (struct ECMonitoredJob *job);

00056 struct ECCupsJobMonitorPrivate
{
      guint total_jobs;
      GHashTable *active_queues; /* char * -> GList(ECMonitoredJob) *  */

      guint poll_all_queues_id;

      gboolean polling;
      GHashTable *removed_jobs;
};

enum
{
      JOB_CHANGED,
      JOB_TIMEOUT,
      LAST_SIGNAL
};

enum
{
      PROP_0,
};

static GObjectClass *parent_class = NULL;

static guint ec_cups_job_monitor_signals[LAST_SIGNAL] = { 0 };

static void
ec_cups_job_monitor_class_init (ECCupsJobMonitorClass *klass)
{
      GObjectClass *object_class = G_OBJECT_CLASS (klass);

      parent_class = g_type_class_peek_parent (klass);

      object_class->dispose = ec_cups_job_monitor_dispose;
      object_class->finalize = ec_cups_job_monitor_finalize;

      ec_cups_job_monitor_signals[JOB_CHANGED] =
            g_signal_new ("job-changed",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (ECCupsJobMonitorClass, job_changed),
                        NULL, NULL,
                        g_cclosure_marshal_VOID__POINTER,
                        G_TYPE_NONE,
                        1,
                        G_TYPE_POINTER);
      ec_cups_job_monitor_signals[JOB_TIMEOUT] =
            g_signal_new ("job-timeout",
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (ECCupsJobMonitorClass, job_timeout),
                        NULL, NULL,
                        ec_marshal_VOID__STRING_UINT_POINTER,
                        G_TYPE_NONE,
                        3,
                        G_TYPE_STRING,
                        G_TYPE_UINT,
                        G_TYPE_POINTER);
}

static void
ec_cups_job_monitor_init (ECCupsJobMonitor *mon)
{
      mon->priv = g_new0 (ECCupsJobMonitorPrivate, 1);

      mon->priv->active_queues = g_hash_table_new_full (g_str_hash, g_str_equal,
                                            (GDestroyNotify) g_free,
                                            NULL);
      mon->priv->removed_jobs = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           (GDestroyNotify) g_free,
                                           NULL);
}

static gboolean
free_queue_item (char *host, GList *list, gpointer unused)
{
      g_free (host);
      g_list_foreach (list, (GFunc) monitored_job_destroy, NULL);
      g_list_free (list);
      return TRUE;
}

static gboolean
free_removed_item (char *host, GList *list, gpointer unused)
{
      g_free (host);
      g_list_free (list);
      return TRUE;
}

static void
ec_cups_job_monitor_dispose (GObject *object)
{
      ECCupsJobMonitor *mon = EC_CUPS_JOB_MONITOR (object);

      g_return_if_fail (mon->priv != NULL);

      g_hash_table_foreach_remove (mon->priv->active_queues,
                             (GHRFunc) free_queue_item,
                             NULL);
      g_hash_table_foreach_remove (mon->priv->removed_jobs,
                             (GHRFunc) free_removed_item,
                             NULL);
}

static void
ec_cups_job_monitor_finalize (GObject *object)
{
      ECCupsJobMonitor *mon = EC_CUPS_JOB_MONITOR (object);

      g_return_if_fail (mon->priv != NULL);

      g_hash_table_destroy (mon->priv->active_queues);
      g_hash_table_destroy (mon->priv->removed_jobs);

      g_free (mon->priv);

      G_OBJECT_CLASS (parent_class)->finalize (object);
}

ECCupsJobMonitor *
ec_cups_job_monitor_new (void)
{
      return EC_CUPS_JOB_MONITOR (g_object_new (EC_TYPE_CUPS_JOB_MONITOR, NULL));
}

static const char *
jstate_to_string (ipp_jstate_t state)
{
      switch (state) {
      case IPP_JOB_PENDING:
            return "PENDING";
      case IPP_JOB_HELD:
            return "HELD";
      case IPP_JOB_PROCESSING:
            return "PROCESSING";
      case IPP_JOB_STOPPED:
            return "STOPPED";
      case IPP_JOB_CANCELLED:
            return "CANCELLED";
      case IPP_JOB_ABORTED:
            return "ABORTED";
      case IPP_JOB_COMPLETED:
            return "COMPLETED";
      }
      g_assert_not_reached ();
      return NULL;
}

static gboolean
job_is_done (GnomeCupsJob *job)
{
      return job->state == IPP_JOB_COMPLETED
            || job->state == IPP_JOB_ABORTED
            || job->state == IPP_JOB_CANCELLED;
}

static void
handle_job_changed (ECCupsJobMonitor *mon,
                const char *host,
                GnomeCupsJob *job)
{
      rb_debug ("handling job change: %d %s %s",
              job->id,
              job->owner,
              jstate_to_string (job->state));

      if (job_is_done (job)) 
            remove_job_deferred (mon, host, job->id);
      g_signal_emit (G_OBJECT (mon),
                   ec_cups_job_monitor_signals[JOB_CHANGED],
                   0, job);
}

/* Stolen from libgnomecups source */
static const char *job_state_strings[] = 
{
      N_("Unknown"),
      N_("Unknown"),
      N_("Unknown"),
      N_("Pending"),
      N_("Paused"),
      N_("Printing"),
      N_("Stopped"),
      N_("Cancelled"),
      N_("Aborted"),
      N_("Completed")
};
static void
finish_job (GnomeCupsJob *job)
{
      const char *str;

      if (!job->name[0]) {
            g_free (job->name);
            job->name = g_strdup (_("Unknown"));
      }
      
      str = (job->state <= IPP_JOB_COMPLETED) ? _(job_state_strings[job->state]) : _("Unknown");
      
      job->state_str = g_strdup (str);
      
      if (job->state_reason && 
          job->state_reason[0] && 
          strcmp (job->state_str, job->state_reason)) {
            job->full_state = g_strdup_printf ("%s: %s", 
                                       job->state_str,
                                       job->state_reason);
      } else {
            job->full_state = g_strdup (job->state_str);
      }

      job->size = job->size * 1024;
}

/* Stolen from libgnomecups source */
#define MAP_STR(dest, src) { if (!g_ascii_strcasecmp (attr->name, (src))) { (dest) = g_strdup (attr->values[0].string.text);}}
#define MAP_INT(dest, src) { if (!g_ascii_strcasecmp (attr->name, (src))) { (dest) = attr->values[0].integer; } }
static GnomeCupsJob *
response_to_job (ipp_t *response, GError *error)
{
      GnomeCupsJob *job = NULL;

      if (error) {
            rb_debug ("caught error doing job get: %s", error->message);
            ippDelete (response);
            g_error_free (error);
            job = NULL;
      } else if (response) {
            ipp_attribute_t *attr;

            job = g_new0 (GnomeCupsJob, 1);
            for (attr = response->attrs; attr != NULL; attr = attr->next) {
                  if (attr->name == NULL) {
                        if (job->name) {
                              finish_job (job);
                        } else {
                              gnome_cups_job_free (job);
                              job = NULL;
                        }
                        break;
                  }
                  
                  if (!g_ascii_strcasecmp (attr->name, "attributes-charset") || !g_ascii_strcasecmp (attr->name, "attributes-charset")) {
                        continue;
                  }
                  
                  MAP_STR (job->name, "job-name");
                  MAP_INT (job->id, "job-id");
                  MAP_STR (job->owner, "job-originating-user-name");
                  MAP_INT (job->size, "job-k-octets");
                  MAP_INT (job->state, "job-state");
                  MAP_STR (job->state_reason, "job-state-reasons");
                  MAP_INT (job->pages, "job-media-sheets");
                  MAP_INT (job->pages_complete, "job-media-sheets-completed");
                  MAP_INT (job->creation_time, "time-at-creation");
                  MAP_INT (job->processing_time, "time-at-processing");
                  MAP_INT (job->completed_time, "time-at-completed");
            }
            
            if (job->name) {
                  finish_job (job);
            } else {
                  gnome_cups_job_free (job);
                  job = NULL;
            }

            ippDelete (response);
      }

      return job;
}

static GnomeCupsJob *
get_job_from_server (const char *server, const char *path, int job_id)
{
      GError *error = NULL;
      ipp_t *request;
      ipp_t *response;  

      request = gnome_cups_request_new_for_job (IPP_GET_JOB_ATTRIBUTES, job_id);
      response = gnome_cups_request_execute (request, server, path, &error);

      return response_to_job (response, error);
}

static gboolean
check_emit_job_timeout (ECCupsJobMonitor *mon, const char *host,
                  struct ECMonitoredJob *monjob)
{
      GTimeVal current_time;

      g_get_current_time (&current_time);
      if ((current_time.tv_sec - monjob->last_update.tv_sec) > TIMEOUT_SECONDS) {
            rb_debug ("job %u on %s has timed out!", monjob->id, host);
            g_signal_emit (G_OBJECT (mon),
                         ec_cups_job_monitor_signals[JOB_TIMEOUT],
                         0,
                         host,
                         monjob->id,
                         &monjob->last_update);
            return TRUE;
      }
      return FALSE;
}

00363 struct ECJobData
{
      ECCupsJobMonitor *mon;
      char *server;
      struct ECMonitoredJob *monjob;
};

static void
destroy_job_data (gpointer data)
{
      struct ECJobData *mondata = data;
      g_free (mondata->server);
      g_free (mondata);
}

static void
get_job_cb (guint id, const char *path, ipp_t *response,
          GError **error, gpointer data)
{
      struct ECJobData *mondata = data;
      ECCupsJobMonitor *mon = mondata->mon;
      struct ECMonitoredJob *monjob = mondata->monjob;
      GnomeCupsJob *current_job = NULL;

      GDK_THREADS_ENTER ();
      
      current_job = response_to_job (response, error ? *error : NULL);

      rb_debug ("got callback for job %u on %s", monjob->id, mondata->server);
      if (!monjob->job && current_job) {
            /* This is the first time we've checked this job;
             * it does exist on the remote server.
             */

            monjob->job = current_job;
            g_get_current_time (&monjob->last_update);
            rb_debug ("job %u FOUND", monjob->id);
            handle_job_changed (mon, mondata->server, current_job);
      } else if (monjob->job && current_job) {
            /* We've seen this job before, and it still exists. */

            g_get_current_time (&monjob->last_update);
            if (!gnome_cups_jobs_equal (monjob->job, current_job)) {
                  gnome_cups_job_free (monjob->job);
                  monjob->job = current_job;
                  handle_job_changed (mon, mondata->server, current_job);
            } else {
                  rb_debug ("job %u UNCHANGED", monjob->id);
                  gnome_cups_job_free (current_job);
            }
      } else {
            /* This job has mysteriously vanished from the remote
             * server.
             */
            rb_debug ("job %u has vanished into the BITBUCKET!", monjob->id);
            check_emit_job_timeout (mon, mondata->server, monjob);
      }
      
      GDK_THREADS_LEAVE ();
}

static void
poll_job (ECCupsJobMonitor *mon, const char *host, struct ECMonitoredJob *monjob)
{
      struct ECJobData *mondata;

      if (monjob->async_query_id > 0 && check_emit_job_timeout (mon, host, monjob)) {
            rb_debug ("cancelling request %u for job %u on %s", monjob->async_query_id,
                    monjob->id, host);
            gnome_cups_request_cancel (monjob->async_query_id);
            monjob->async_query_id = 0;
      }

      if (monjob->async_query_id == 0) {
            ipp_t *request;

            mondata = g_new0 (struct ECJobData, 1);
            mondata->mon = mon;
            mondata->server = g_strdup (host);
            mondata->monjob = monjob;
            
            request = gnome_cups_request_new_for_job (IPP_GET_JOB_ATTRIBUTES,
                                            monjob->id);
            monjob->async_query_id = 
                  gnome_cups_request_execute_async (request, host,
                                            monjob->path,
                                            get_job_cb,
                                            mondata,
                                            destroy_job_data);
            rb_debug ("kicking off request %u for job %u on %s",
                    monjob->async_query_id, monjob->id, host);
      } 
}

static void
poll_jobs (const char *host, GList *jobs, ECCupsJobMonitor *mon)
{
      rb_debug ("polling %s", host);
      for (; jobs; jobs = jobs->next)
            poll_job (mon, host, (struct ECMonitoredJob *) jobs->data);
}

static gboolean
process_deferred_removals (const char *host, GList *job_id_pointers , ECCupsJobMonitor *mon)
{
      GList *job;

      rb_debug ("processing deferred removals on %s", host);

      for (job = job_id_pointers; job; job = job->next)
            remove_job_internal (mon, host, GPOINTER_TO_UINT (job->data));
      return TRUE;
}

static gboolean
poll_all_queues (ECCupsJobMonitor *mon)
{
      GDK_THREADS_ENTER ();
      
      rb_debug ("polling all queues");

      mon->priv->polling = TRUE;

      g_hash_table_foreach_remove (mon->priv->removed_jobs, (GHRFunc) process_deferred_removals, mon); 

      g_hash_table_foreach (mon->priv->active_queues, (GHFunc) poll_jobs, mon); 

      g_hash_table_foreach_remove (mon->priv->removed_jobs, (GHRFunc) process_deferred_removals, mon); 

      if (mon->priv->total_jobs > 0) {
            rb_debug ("%d remaining jobs to monitor", mon->priv->total_jobs); 
            mon->priv->poll_all_queues_id = g_timeout_add (POLL_MSEC, (GSourceFunc) poll_all_queues, mon);
      } else {
            rb_debug ("no more jobs to monitor; not requeuing idle poll"); 
            mon->priv->poll_all_queues_id = 0;
      }

      mon->priv->polling = FALSE;

      GDK_THREADS_LEAVE ();

      return FALSE;
}

static int
find_job_by_id (struct ECMonitoredJob *monjob, gpointer job_id_pointer)
{
      guint32 id = GPOINTER_TO_UINT (job_id_pointer);

      if (monjob->id == id)
            return 0;
      return monjob->id > id ? -1 : 1;
}

static void
lookup_job (ECCupsJobMonitor *mon, const char *host,
          guint32 id, GList **jobs, GList **link)
{
      *jobs = NULL;
      *link = NULL;
      
      *jobs = g_hash_table_lookup (mon->priv->active_queues, host);
      if (!*jobs)
            return;
      
      *link = g_list_find_custom (*jobs, GUINT_TO_POINTER (id), (GCompareFunc) find_job_by_id);
}

GnomeCupsJob *
ec_cups_job_monitor_add_job (ECCupsJobMonitor *mon,
                       gboolean poll_now,
                       const char *host,
                       const char *printer_path,
                       guint32 job_id)
{
      GList *jobs;
      struct ECMonitoredJob *monjob;
      GnomeCupsJob *ret;

      g_return_val_if_fail (mon->priv->polling == FALSE, NULL);

      rb_debug ("monitoring job %d on %s (%s)", job_id, host, printer_path);

      jobs = g_hash_table_lookup (mon->priv->active_queues, host);

      monjob = monitored_job_new (job_id, printer_path);
      jobs = g_list_prepend (jobs, monjob);
            
      mon->priv->total_jobs++;
      g_hash_table_insert (mon->priv->active_queues, g_strdup (host), jobs);

      if (mon->priv->poll_all_queues_id == 0) {
            rb_debug ("first job; kicking off polling");
            mon->priv->poll_all_queues_id = g_idle_add ((GSourceFunc) poll_all_queues,
                                              mon);
      }

      if (poll_now)
            ret = get_job_from_server (host, monjob->path, monjob->id);
      else
            ret = NULL;
      return ret;
}

static void
remove_job_internal (ECCupsJobMonitor *mon,
                 const char *host,
                 guint32 job_id)
{
      GList *jobs;
      GList *link;

      lookup_job (mon, host, job_id, &jobs, &link);
      g_return_if_fail (link != NULL);

      rb_debug ("unmonitoring job %u on %s", job_id, host);

      mon->priv->total_jobs--;

      monitored_job_destroy (link->data);
      jobs = g_list_remove_link (jobs, link);
      g_list_free (link);

      rb_debug ("%d jobs remaining on %s", g_list_length (jobs), host);
      if (jobs == NULL) {
            g_hash_table_remove (mon->priv->active_queues, host);
      } else {
            g_hash_table_insert (mon->priv->active_queues,
                             g_strdup (host), jobs);
      }
}

static void
remove_job_deferred (ECCupsJobMonitor *mon,
                 const char *host,
                 guint32 job_id)
{
      GList *remjobs;

      /* Defer this removal until later; we've been called from a callback
       * to one of our signal handlers that is iterating over the hash table.
       */

      rb_debug ("deferring removal of %u", job_id);
      remjobs = g_hash_table_lookup (mon->priv->removed_jobs, host);
      remjobs = g_list_prepend (remjobs, GUINT_TO_POINTER (job_id));
      g_hash_table_insert (mon->priv->removed_jobs,
                       g_strdup (host), remjobs); 
}

void
ec_cups_job_monitor_remove_job (ECCupsJobMonitor *mon,
                        const char *host,
                        guint32 job_id)
{
      rb_debug ("user asked to remove job %d on %s", job_id, host); 

      if (mon->priv->polling) {
            remove_job_deferred (mon, host, job_id);
            return;
      }

      remove_job_internal (mon, host, job_id);
      
      if (mon->priv->total_jobs == 0) {
            rb_debug ("no more jobs; stopping all polling");
            g_source_remove (mon->priv->poll_all_queues_id);
            mon->priv->poll_all_queues_id = 0;
      }
}

static void
count_removed_jobs (const char *host, GList *list, guint *count)
{
      *count += g_list_length (list);
}

guint
ec_cups_job_monitor_get_job_count (ECCupsJobMonitor *mon)
{
      guint removed_job_count = 0;
      g_hash_table_foreach (mon->priv->removed_jobs, (GHFunc) count_removed_jobs, &removed_job_count);
      return mon->priv->total_jobs - removed_job_count;
}

static struct ECMonitoredJob *
monitored_job_new (guint32 id, const char *path)
{
      struct ECMonitoredJob *monjob = g_new0 (struct ECMonitoredJob, 1);
      monjob->id = id;
      monjob->path = g_strdup (path);
      rb_debug ("creating job %d", monjob->id);
      return monjob;
}

static void
monitored_job_destroy (struct ECMonitoredJob *job)
{
      rb_debug ("unreffing job %d (%s)", job->id, job->path);

      g_free (job->path);
      if (job->job)
            gnome_cups_job_free (job->job);
      if (job->async_query_id > 0)
            gnome_cups_request_cancel (job->async_query_id);
      g_free (job);
}


Generated by  Doxygen 1.6.0   Back to index