]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/job.c
Merge changes from CUPS 1.5svn-r9400
[thirdparty/cups.git] / scheduler / job.c
index 669e70f07378a73a25204d963ad408d3edb6c17a..94fc08844a5cfeeb4b36b1aa7fd5fb80a26ab635 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * "$Id: job.c 7902 2008-09-03 14:20:17Z mike $"
  *
- *   Job management routines for the Common UNIX Printing System (CUPS).
+ *   Job management routines for the CUPS scheduler.
  *
- *   Copyright 2007-2009 by Apple Inc.
+ *   Copyright 2007-2010 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   These coded instructions, statements, and computer programs are the
@@ -168,12 +168,12 @@ static mime_filter_t      gziptoany_filter =
 static int     compare_active_jobs(void *first, void *second, void *data);
 static int     compare_jobs(void *first, void *second, void *data);
 static void    dump_job_history(cupsd_job_t *job);
-static void    finalize_job(cupsd_job_t *job);
+static void    finalize_job(cupsd_job_t *job, int set_job_state);
 static void    free_job_history(cupsd_job_t *job);
 static char    *get_options(cupsd_job_t *job, int banner_page, char *copies,
                             size_t copies_size, char *title,
                             size_t title_size);
-static int     ipp_length(ipp_t *ipp);
+static size_t  ipp_length(ipp_t *ipp);
 static void    load_job_cache(const char *filename);
 static void    load_next_job_id(const char *filename);
 static void    load_request_root(void);
@@ -253,7 +253,7 @@ cupsdCancelJobs(const char *dest,   /* I - Destination to cancel */
       if (purge)
        cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_PURGE,
                         "Job purged by user.");
-      else
+      else if (job->state_value < IPP_JOB_CANCELED)
        cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
                         "Job canceled by user.");
     }
@@ -294,6 +294,9 @@ cupsdCheckJobs(void)
 
     if (job->kill_time && job->kill_time <= curtime)
     {
+      cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Stopping unresponsive job!", 
+                     job->id);
+
       stop_job(job, CUPSD_JOB_FORCE);
       continue;
     }
@@ -348,7 +351,7 @@ cupsdCheckJobs(void)
     */
 
     if (job->state_value == IPP_JOB_PENDING && !NeedReload && !Sleeping &&
-        !job->printer)
+        !DoingShutdown && !job->printer)
     {
       printer = cupsdFindDest(job->dest);
       pclass  = NULL;
@@ -433,11 +436,11 @@ cupsdCleanJobs(void)
   cupsd_job_t  *job;                   /* Current job */
 
 
-  if (MaxJobs <= 0)
+  if (MaxJobs <= 0 && JobHistory)
     return;
 
   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
-       job && cupsArrayCount(Jobs) >= MaxJobs;
+       job && (cupsArrayCount(Jobs) >= MaxJobs || !JobHistory);
        job = (cupsd_job_t *)cupsArrayNext(Jobs))
     if (job->state_value >= IPP_JOB_CANCELED && !job->printer)
       cupsdDeleteJob(job, CUPSD_JOB_PURGE);
@@ -1233,7 +1236,7 @@ cupsdDeleteJob(cupsd_job_t       *job,    /* I - Job */
                cupsd_jobaction_t action)/* I - Action */
 {
   if (job->printer)
-    finalize_job(job);
+    finalize_job(job, 1);
 
   if (action == CUPSD_JOB_PURGE)
   {
@@ -1657,7 +1660,30 @@ cupsdLoadJob(cupsd_job_t *job)           /* I - Job */
           cupsdLogMessage(CUPSD_LOG_ERROR,
                          "[Job %d] Ran out of memory for job file types!",
                          job->id);
-         return (1);
+
+         ippDelete(job->attrs);
+         job->attrs = NULL;
+
+         if (compressions)
+           free(compressions);
+
+         if (filetypes)
+           free(filetypes);
+
+         if (job->compressions)
+         {
+           free(job->compressions);
+           job->compressions = NULL;
+         }
+
+         if (job->filetypes)
+         {
+           free(job->filetypes);
+           job->filetypes = NULL;
+         }
+
+         job->num_files = 0;
+         return (0);
        }
 
         job->compressions = compressions;
@@ -1725,6 +1751,21 @@ cupsdLoadJob(cupsd_job_t *job)           /* I - Job */
 
   ippDelete(job->attrs);
   job->attrs = NULL;
+
+  if (job->compressions)
+  {
+    free(job->compressions);
+    job->compressions = NULL;
+  }
+
+  if (job->filetypes)
+  {
+    free(job->filetypes);
+    job->filetypes = NULL;
+  }
+
+  job->num_files = 0;
+
   unlink(jobfile);
 
   return (0);
@@ -2184,19 +2225,21 @@ cupsdSetJobState(
   if (!cupsdLoadJob(job))
     return;
 
- /*
-  * Don't do anything if the state is unchanged...
-  */
+  /*
+   * Don't do anything if the state is unchanged and we aren't purging the
+   * job...
+   */
 
-  if (newstate == (oldstate = job->state_value))
-    return;
+   oldstate = job->state_value;
+   if (newstate == oldstate && action != CUPSD_JOB_PURGE)
+     return;
 
  /*
   * Stop any processes that are working on the current job...
   */
 
   if (oldstate == IPP_JOB_PROCESSING)
-    stop_job(job, action != CUPSD_JOB_DEFAULT);
+    stop_job(job, action);
 
  /*
   * Set the new job state...
@@ -2397,7 +2440,14 @@ cupsdSetJobState(
          cupsdMarkDirty(CUPSD_DIRTY_JOBS);
        }
        else if (!job->printer)
+       {
+        /*
+         * Delete the job immediately if not actively printing...
+         */
+
          cupsdDeleteJob(job, CUPSD_JOB_PURGE);
+         job = NULL;
+       }
        break;
   }
 
@@ -2405,8 +2455,8 @@ cupsdSetJobState(
   * Finalize the job immediately if we forced things...
   */
 
-  if (action == CUPSD_JOB_FORCE)
-    finalize_job(job);
+  if (action >= CUPSD_JOB_FORCE && job && job->printer)
+    finalize_job(job, 0);
 
  /*
   * Update the server "busy" state...
@@ -2641,7 +2691,8 @@ free_job_history(cupsd_job_t *job)        /* I - Job */
  */
 
 static void
-finalize_job(cupsd_job_t *job)         /* I - Job */
+finalize_job(cupsd_job_t *job,         /* I - Job */
+             int         set_job_state)        /* I - 1 = set the job state */
 {
   ipp_pstate_t         printer_state;  /* New printer state value */
   ipp_jstate_t         job_state;      /* New job state value */
@@ -2652,12 +2703,11 @@ finalize_job(cupsd_job_t *job)          /* I - Job */
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id);
 
  /*
-  * Clear the "connecting-to-device" and "com.apple.print.recoverable-warning"
-  * reasons, which are only valid when a printer is processing...
+  * Clear the "connecting-to-device" reason, which is only valid when a printer
+  * is processing...
   */
 
   cupsdSetPrinterReasons(job->printer, "-connecting-to-device");
-  cupsdSetPrinterReasons(job->printer, "-com.apple.print.recoverable-warning");
 
  /*
   * Similarly, clear the "offline-report" reason for non-USB devices since we
@@ -2674,6 +2724,12 @@ finalize_job(cupsd_job_t *job)           /* I - Job */
   cupsdDestroyProfile(job->profile);
   job->profile = NULL;
 
+ /*
+  * Clear the unresponsive job watchdog timer...
+  */
+
+  job->kill_time = 0;
+
  /*
   * Close pipes and status buffer...
   */
@@ -2809,7 +2865,7 @@ finalize_job(cupsd_job_t *job)            /* I - Job */
 
              job->tries ++;
 
-             if (job->tries >= JobRetryLimit)
+             if (job->tries > JobRetryLimit && JobRetryLimit > 0)
              {
               /*
                * Too many tries...
@@ -2927,7 +2983,9 @@ finalize_job(cupsd_job_t *job)            /* I - Job */
   * Update the printer and job state.
   */
 
-  cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
+  if (job_state != job->state_value)
+    cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
+
   cupsdSetPrinterState(job->printer, printer_state,
                        printer_state == IPP_PRINTER_STOPPED);
   update_job_attrs(job, 0);
@@ -2971,39 +3029,183 @@ get_options(cupsd_job_t *job,          /* I - Job */
            size_t      title_size)     /* I - Size of title buffer */
 {
   int                  i;              /* Looping var */
+  size_t               newlength;      /* New option buffer length */
   char                 *optptr,        /* Pointer to options */
                        *valptr;        /* Pointer in value string */
   ipp_attribute_t      *attr;          /* Current attribute */
+  _pwg_t               *pwg;           /* PWG->PPD mapping data */
+  int                  num_pwgppds;    /* Number of PWG->PPD options */
+  cups_option_t                *pwgppds,       /* PWG->PPD options */
+                       *pwgppd,        /* Current PWG->PPD option */
+                       *preset;        /* Current preset option */
+  int                  output_mode,    /* Output mode (if any) */
+                       print_quality;  /* Print quality (if any) */
+  const char           *ppd;           /* PPD option choice */
+  int                  exact;          /* Did we get an exact match? */
   static char          *options = NULL;/* Full list of options */
-  static int           optlength = 0;  /* Length of option buffer */
+  static size_t                optlength = 0;  /* Length of option buffer */
 
 
  /*
-  * Building the options string is harder than it needs to be, but
-  * for the moment we need to pass strings for command-line args and
-  * not IPP attribute pointers... :)
+  * Building the options string is harder than it needs to be, but for the
+  * moment we need to pass strings for command-line args and not IPP attribute
+  * pointers... :)
   *
-  * First allocate/reallocate the option buffer as needed...
+  * First build an options array for any PWG->PPD mapped option/choice pairs.
   */
 
-  i = ipp_length(job->attrs);
+  pwg         = job->printer->pwg;
+  num_pwgppds = 0;
+  pwgppds     = NULL;
+
+  if (pwg &&
+      !ippFindAttribute(job->attrs,
+                        "com.apple.print.DocumentTicket.PMSpoolFormat",
+                       IPP_TAG_ZERO) &&
+      (ippFindAttribute(job->attrs, "output-mode", IPP_TAG_ZERO) ||
+       ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ZERO)))
+  {
+   /*
+    * Map output-mode and print-quality to a preset...
+    */
+
+    if ((attr = ippFindAttribute(job->attrs, "output-mode",
+                                IPP_TAG_KEYWORD)) != NULL &&
+       !strcmp(attr->values[0].string.text, "monochrome"))
+      output_mode = _PWG_OUTPUT_MODE_MONOCHROME;
+    else
+      output_mode = _PWG_OUTPUT_MODE_COLOR;
+
+    if ((attr = ippFindAttribute(job->attrs, "print-quality",
+                                IPP_TAG_ENUM)) != NULL &&
+       attr->values[0].integer >= IPP_QUALITY_DRAFT &&
+       attr->values[0].integer <= IPP_QUALITY_HIGH)
+      print_quality = attr->values[0].integer - IPP_QUALITY_DRAFT;
+    else
+      print_quality = _PWG_PRINT_QUALITY_NORMAL;
+
+    if (pwg->num_presets[output_mode][print_quality] == 0)
+    {
+     /*
+      * Try to find a preset that works so that we maximize the chances of us
+      * getting a good print using IPP attributes.
+      */
+
+      if (pwg->num_presets[output_mode][_PWG_PRINT_QUALITY_NORMAL] > 0)
+        print_quality = _PWG_PRINT_QUALITY_NORMAL;
+      else if (pwg->num_presets[_PWG_OUTPUT_MODE_COLOR][print_quality] > 0)
+        output_mode = _PWG_OUTPUT_MODE_COLOR;
+      else
+      {
+        print_quality = _PWG_PRINT_QUALITY_NORMAL;
+        output_mode   = _PWG_OUTPUT_MODE_COLOR;
+      }
+    }
+
+    if (pwg->num_presets[output_mode][print_quality] > 0)
+    {
+     /*
+      * Copy the preset options as long as the corresponding names are not
+      * already defined in the IPP request...
+      */
+
+      for (i = pwg->num_presets[output_mode][print_quality],
+              preset = pwg->presets[output_mode][print_quality];
+          i > 0;
+          i --, preset ++)
+      {
+        if (!ippFindAttribute(job->attrs, preset->name, IPP_TAG_ZERO))
+         num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds,
+                                     &pwgppds);
+      }
+    }
+  }
 
-  if (i > optlength || !options)
+  if (pwg)
+  {
+    if (pwg->sides_option &&
+        !ippFindAttribute(job->attrs, pwg->sides_option, IPP_TAG_ZERO) &&
+       (attr = ippFindAttribute(job->attrs, "sides", IPP_TAG_KEYWORD)) != NULL)
+    {
+     /*
+      * Add a duplex option...
+      */
+
+      if (!strcmp(attr->values[0].string.text, "one-sided"))
+        num_pwgppds = cupsAddOption(pwg->sides_option, pwg->sides_1sided,
+                                   num_pwgppds, &pwgppds);
+      else if (!strcmp(attr->values[0].string.text, "two-sided-long-edge"))
+        num_pwgppds = cupsAddOption(pwg->sides_option, pwg->sides_2sided_long,
+                                   num_pwgppds, &pwgppds);
+      else if (!strcmp(attr->values[0].string.text, "two-sided-short-edge"))
+        num_pwgppds = cupsAddOption(pwg->sides_option, pwg->sides_2sided_short,
+                                   num_pwgppds, &pwgppds);
+    }
+
+    if (!ippFindAttribute(job->attrs, "InputSlot", IPP_TAG_ZERO) &&
+       !ippFindAttribute(job->attrs, "HPPaperSource", IPP_TAG_ZERO))
+    {
+      if ((ppd = _pwgGetInputSlot(pwg, job->attrs, NULL)) != NULL)
+       num_pwgppds = cupsAddOption(pwg->source_option, ppd, num_pwgppds,
+                                   &pwgppds);
+      else if (!ippFindAttribute(job->attrs, "AP_D_InputSlot", IPP_TAG_ZERO))
+       num_pwgppds = cupsAddOption("AP_D_InputSlot", "", num_pwgppds,
+                                   &pwgppds);
+    }
+
+    if (!ippFindAttribute(job->attrs, "MediaType", IPP_TAG_ZERO) &&
+       (ppd = _pwgGetMediaType(pwg, job->attrs, NULL)) != NULL)
+      num_pwgppds = cupsAddOption("MediaType", ppd, num_pwgppds, &pwgppds);
+
+    if (!ippFindAttribute(job->attrs, "PageRegion", IPP_TAG_ZERO) &&
+       !ippFindAttribute(job->attrs, "PageSize", IPP_TAG_ZERO) &&
+       (ppd = _pwgGetPageSize(pwg, job->attrs, NULL, &exact)) != NULL)
+    {
+      num_pwgppds = cupsAddOption("PageSize", ppd, num_pwgppds, &pwgppds);
+
+      if (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO))
+        num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds);
+    }
+
+    if (!ippFindAttribute(job->attrs, "OutputBin", IPP_TAG_ZERO) &&
+       (attr = ippFindAttribute(job->attrs, "output-bin",
+                                IPP_TAG_ZERO)) != NULL &&
+       (attr->value_tag == IPP_TAG_KEYWORD ||
+        attr->value_tag == IPP_TAG_NAME) &&
+       (ppd = _pwgGetOutputBin(pwg, attr->values[0].string.text)) != NULL) 
+      num_pwgppds = cupsAddOption("OutputBin", ppd, num_pwgppds, &pwgppds);
+  }
+
+ /*
+  * Figure out how much room we need...
+  */
+
+  newlength = ipp_length(job->attrs);
+
+  for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
+    newlength += 1 + strlen(pwgppd->name) + 1 + strlen(pwgppd->value);
+
+ /*
+  * Then allocate/reallocate the option buffer as needed...
+  */
+
+  if (newlength > optlength || !options)
   {
     if (!options)
-      optptr = malloc(i);
+      optptr = malloc(newlength);
     else
-      optptr = realloc(options, i);
+      optptr = realloc(options, newlength);
 
     if (!optptr)
     {
       cupsdLogJob(job, CUPSD_LOG_CRIT,
-                 "Unable to allocate %d bytes for option buffer!", i);
+                 "Unable to allocate " CUPS_LLFMT " bytes for option buffer!",
+                 CUPS_LLCAST newlength);
       return (NULL);
     }
 
     options   = optptr;
-    optlength = i;
+    optlength = newlength;
   }
 
  /*
@@ -3039,7 +3241,8 @@ get_options(cupsd_job_t *job,             /* I - Job */
       * Filter out other unwanted attributes...
       */
 
-      if (attr->value_tag == IPP_TAG_MIMETYPE ||
+      if (attr->value_tag == IPP_TAG_NOVALUE ||
+          attr->value_tag == IPP_TAG_MIMETYPE ||
          attr->value_tag == IPP_TAG_NAMELANG ||
          attr->value_tag == IPP_TAG_TEXTLANG ||
          (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid")) ||
@@ -3047,8 +3250,7 @@ get_options(cupsd_job_t *job,             /* I - Job */
          attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
        continue;
 
-      if (!strncmp(attr->name, "time-", 5) ||
-          !strcmp(attr->name, "job-hold-until"))
+      if (!strcmp(attr->name, "job-hold-until"))
        continue;
 
       if (!strncmp(attr->name, "job-", 4) &&
@@ -3106,7 +3308,6 @@ get_options(cupsd_job_t *job,             /* I - Job */
              if (!attr->values[i].boolean)
                strlcat(optptr, "no", optlength - (optptr - options));
 
-         case IPP_TAG_NOVALUE :
              strlcat(optptr, attr->name,
                      optlength - (optptr - options));
              break;
@@ -3155,6 +3356,25 @@ get_options(cupsd_job_t *job,            /* I - Job */
     }
   }
 
+ /*
+  * Finally loop through the PWG->PPD mapped options and add them...
+  */
+
+  for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
+  {
+    *optptr++ = ' ';
+    strcpy(optptr, pwgppd->name);
+    optptr += strlen(optptr);
+    *optptr++ = '=';
+    strcpy(optptr, pwgppd->value);
+    optptr += strlen(optptr);
+  }
+
+  cupsFreeOptions(num_pwgppds, pwgppds);
+
+ /*
+  * Return the options string...
+  */
 
   return (options);
 }
@@ -3165,10 +3385,10 @@ get_options(cupsd_job_t *job,           /* I - Job */
  *                 the textual IPP attributes.
  */
 
-static int                             /* O - Size of attribute buffer */
+static size_t                          /* O - Size of attribute buffer */
 ipp_length(ipp_t *ipp)                 /* I - IPP request */
 {
-  int                  bytes;          /* Number of bytes */
+  size_t               bytes;          /* Number of bytes */
   int                  i;              /* Looping var */
   ipp_attribute_t      *attr;          /* Current attribute */
 
@@ -3185,16 +3405,14 @@ ipp_length(ipp_t *ipp)                  /* I - IPP request */
     * Skip attributes that won't be sent to filters...
     */
 
-    if (attr->value_tag == IPP_TAG_MIMETYPE ||
+    if (attr->value_tag == IPP_TAG_NOVALUE ||
+       attr->value_tag == IPP_TAG_MIMETYPE ||
        attr->value_tag == IPP_TAG_NAMELANG ||
        attr->value_tag == IPP_TAG_TEXTLANG ||
        attr->value_tag == IPP_TAG_URI ||
        attr->value_tag == IPP_TAG_URISCHEME)
       continue;
 
-    if (strncmp(attr->name, "time-", 5) == 0)
-      continue;
-
    /*
     * Add space for a leading space and commas between each value.
     * For the first attribute, the leading space isn't used, so the
@@ -3895,15 +4113,34 @@ stop_job(cupsd_job_t       *job,        /* I - Job */
 
   if (action == CUPSD_JOB_DEFAULT && !job->kill_time)
     job->kill_time = time(NULL) + JobKillDelay;
-  else if (action == CUPSD_JOB_FORCE)
+  else if (action >= CUPSD_JOB_FORCE)
     job->kill_time = 0;
 
   for (i = 0; job->filters[i]; i ++)
     if (job->filters[i] > 0)
-      cupsdEndProcess(job->filters[i], action == CUPSD_JOB_FORCE);
+    {
+      cupsdEndProcess(job->filters[i], action >= CUPSD_JOB_FORCE);
+
+      if (action >= CUPSD_JOB_FORCE)
+        job->filters[i] = -job->filters[i];
+    }
 
   if (job->backend > 0)
-    cupsdEndProcess(job->backend, action == CUPSD_JOB_FORCE);
+  {
+    cupsdEndProcess(job->backend, action >= CUPSD_JOB_FORCE);
+
+    if (action >= CUPSD_JOB_FORCE)
+      job->backend = -job->backend;
+  }
+
+  if (action >= CUPSD_JOB_FORCE)
+  {
+   /*
+    * Clear job status...
+    */
+
+    job->status = 0;
+  }
 }
 
 
@@ -3998,28 +4235,7 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
         job->sheets->values[0].integer += copies;
 
        if (job->printer->page_limit)
-       {
-         cupsd_quota_t *q = cupsdUpdateQuota(job->printer, job->username,
-                                             copies, 0);
-
-#ifdef __APPLE__
-         if (AppleQuotas && q->page_count == -3)
-         {
-          /*
-           * Quota limit exceeded, cancel job in progress immediately...
-           */
-
-           cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
-                            "Canceled job because pages exceed user %s "
-                            "quota limit on printer %s (%s).",
-                            job->username, job->printer->name,
-                            job->printer->info);
-           return;
-         }
-#else
-          (void)q;
-#endif /* __APPLE__ */
-       }
+         cupsdUpdateQuota(job->printer, job->username, copies, 0);
       }
 
       cupsdLogPage(job, message);
@@ -4179,53 +4395,39 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
 
       cupsFreeOptions(num_keywords, keywords);
     }
-    else if (!strncmp(message, "recoverable:", 12))
+    else
     {
-      ptr = message + 12;
-      while (isspace(*ptr & 255))
-        ptr ++;
+     /*
+      * Strip legacy message prefix...
+      */
 
-      if (*ptr)
+      if (!strncmp(message, "recoverable:", 12))
       {
-       if (cupsdSetPrinterReasons(job->printer,
-                                  "+com.apple.print.recoverable-warning") ||
-           !job->printer->recoverable ||
-           strcmp(job->printer->recoverable, ptr))
-       {
-         cupsdSetString(&(job->printer->recoverable), ptr);
-         cupsdAddPrinterHistory(job->printer);
-         event |= CUPSD_EVENT_PRINTER_STATE;
-       }
+        ptr = message + 12;
+       while (isspace(*ptr & 255))
+          ptr ++;
       }
-    }
-    else if (!strncmp(message, "recovered:", 10))
-    {
-      ptr = message + 10;
-      while (isspace(*ptr & 255))
-        ptr ++;
-
-      if (cupsdSetPrinterReasons(job->printer,
-                                 "-com.apple.print.recoverable-warning") ||
-         !job->printer->recoverable || strcmp(job->printer->recoverable, ptr))
+      else if (!strncmp(message, "recovered:", 10))
       {
-       cupsdSetString(&(job->printer->recoverable), ptr);
-       cupsdAddPrinterHistory(job->printer);
-       event |= CUPSD_EVENT_PRINTER_STATE;
+        ptr = message + 10;
+       while (isspace(*ptr & 255))
+          ptr ++;
       }
-    }
-    else
-    {
-      cupsdLogJob(job, loglevel, "%s", message);
+      else
+        ptr = message;
+
+      cupsdLogJob(job, loglevel, "%s", ptr);
 
-      if (loglevel < CUPSD_LOG_DEBUG)
+      if (loglevel < CUPSD_LOG_DEBUG &&
+          strcmp(job->printer->state_message, ptr))
       {
-       strlcpy(job->printer->state_message, message,
+       strlcpy(job->printer->state_message, ptr,
                sizeof(job->printer->state_message));
        cupsdAddPrinterHistory(job->printer);
 
        event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS;
 
-       if (loglevel < job->status_level)
+       if (loglevel <= job->status_level && job->status_level > CUPSD_LOG_ERROR)
        {
         /*
          * Some messages show in the job-printer-state-message attribute...
@@ -4249,6 +4451,9 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
       break;
   }
 
+  if (event & CUPSD_EVENT_JOB_PROGRESS)
+    cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
+                  "%s", job->printer->state_message);
   if (event & CUPSD_EVENT_PRINTER_STATE)
     cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL,
                  (job->printer->type & CUPS_PRINTER_CLASS) ?
@@ -4256,9 +4461,6 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
                      "Printer \"%s\" state changed.",
                  job->printer->name);
 
-  if (event & CUPSD_EVENT_JOB_PROGRESS)
-    cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
-                  "%s", job->printer->state_message);
 
   if (ptr == NULL && !job->status_buffer->bufused)
   {
@@ -4293,7 +4495,7 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
     * Handle the end of job stuff...
     */
 
-    finalize_job(job);
+    finalize_job(job, 1);
 
    /*
     * Check for new jobs...
@@ -4347,7 +4549,7 @@ update_job_attrs(cupsd_job_t *job,        /* I - Job to update */
   else if (job->printer->state_message[0] && do_message)
     cupsdSetString(&(job->printer_message->values[0].string.text),
                   job->printer->state_message);
-  
+
  /*
   * ... and the printer-state-reasons value...
   */