set_job_attrs(cupsd_client_t  *con,	/* I - Client connection */
	      ipp_attribute_t *uri)	/* I - Job URI */
{
  ipp_attribute_t	*attr,		/* Current attribute */
			*attr2;		/* Job attribute */
  int			jobid;		/* Job ID */
  cupsd_job_t		*job;		/* Current job */
  char			scheme[HTTP_MAX_URI],
					/* Method portion of URI */
			username[HTTP_MAX_URI],
					/* Username portion of URI */
			host[HTTP_MAX_URI],
					/* Host portion of URI */
			resource[HTTP_MAX_URI];
					/* Resource portion of URI */
  int			port;		/* Port portion of URI */
  int			event;		/* Events? */
  int			check_jobs;	/* Check jobs? */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_job_attrs(%p[%d], %s)", con,
                  con->number, uri->values[0].string.text);

 /*
  * Start with "everything is OK" status...
  */

  con->response->request.status.status_code = IPP_OK;

 /*
  * See if we have a job URI or a printer URI...
  */

  if (!strcmp(uri->name, "printer-uri"))
  {
   /*
    * Got a printer URI; see if we also have a job-id attribute...
    */

    if ((attr = ippFindAttribute(con->request, "job-id",
                                 IPP_TAG_INTEGER)) == NULL)
    {
      send_ipp_status(con, IPP_BAD_REQUEST,
                      _("Got a printer-uri attribute but no job-id."));
      return;
    }

    jobid = attr->values[0].integer;
  }
  else
  {
   /*
    * Got a job URI; parse it to get the job ID...
    */

    httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme,
                    sizeof(scheme), username, sizeof(username), host,
		    sizeof(host), &port, resource, sizeof(resource));

    if (strncmp(resource, "/jobs/", 6))
    {
     /*
      * Not a valid URI!
      */

      send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-uri \"%s\"."),
                      uri->values[0].string.text);
      return;
    }

    jobid = atoi(resource + 6);
  }

 /*
  * See if the job exists...
  */

  if ((job = cupsdFindJob(jobid)) == NULL)
  {
   /*
    * Nope - return a "not found" error...
    */

    send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist."), jobid);
    return;
  }

 /*
  * See if the job has been completed...
  */

  if (job->state_value > IPP_JOB_STOPPED)
  {
   /*
    * Return a "not-possible" error...
    */

    send_ipp_status(con, IPP_NOT_POSSIBLE,
                    _("Job #%d is finished and cannot be altered."), jobid);
    return;
  }

 /*
  * See if the job is owned by the requesting user...
  */

  if (!validate_user(job, con, job->username, username, sizeof(username)))
  {
    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
                    cupsdFindDest(job->dest));
    return;
  }

 /*
  * See what the user wants to change.
  */

  cupsdLoadJob(job);

  check_jobs = 0;
  event      = 0;

  for (attr = con->request->attrs; attr; attr = attr->next)
  {
    if (attr->group_tag != IPP_TAG_JOB || !attr->name)
      continue;

    if (!strcmp(attr->name, "attributes-charset") ||
	!strcmp(attr->name, "attributes-natural-language") ||
	!strncmp(attr->name, "date-time-at-", 13) ||
	!strncmp(attr->name, "document-compression", 20) ||
	!strncmp(attr->name, "document-format", 15) ||
	!strcmp(attr->name, "job-detailed-status-messages") ||
	!strcmp(attr->name, "job-document-access-errors") ||
	!strcmp(attr->name, "job-id") ||
	!strcmp(attr->name, "job-impressions-completed") ||
	!strcmp(attr->name, "job-k-octets-completed") ||
	!strcmp(attr->name, "job-media-sheets-completed") ||
        !strcmp(attr->name, "job-originating-host-name") ||
        !strcmp(attr->name, "job-originating-user-name") ||
	!strcmp(attr->name, "job-pages-completed") ||
	!strcmp(attr->name, "job-printer-up-time") ||
	!strcmp(attr->name, "job-printer-uri") ||
	!strcmp(attr->name, "job-sheets") ||
	!strcmp(attr->name, "job-state-message") ||
	!strcmp(attr->name, "job-state-reasons") ||
	!strcmp(attr->name, "job-uri") ||
	!strcmp(attr->name, "number-of-documents") ||
	!strcmp(attr->name, "number-of-intervening-jobs") ||
	!strcmp(attr->name, "output-device-assigned") ||
	!strncmp(attr->name, "time-at-", 8))
    {
     /*
      * Read-only attrs!
      */

      send_ipp_status(con, IPP_ATTRIBUTES_NOT_SETTABLE,
                      _("%s cannot be changed."), attr->name);

      attr2 = ippCopyAttribute(con->response, attr, 0);
      ippSetGroupTag(con->response, &attr2, IPP_TAG_UNSUPPORTED_GROUP);
      continue;
    }

    if (!strcmp(attr->name, "job-priority"))
    {
     /*
      * Change the job priority...
      */

      if (attr->value_tag != IPP_TAG_INTEGER)
      {
	send_ipp_status(con, IPP_REQUEST_VALUE, _("Bad job-priority value."));

	attr2 = ippCopyAttribute(con->response, attr, 0);
	ippSetGroupTag(con->response, &attr2, IPP_TAG_UNSUPPORTED_GROUP);
      }
      else if (job->state_value >= IPP_JOB_PROCESSING)
      {
	send_ipp_status(con, IPP_NOT_POSSIBLE,
	                _("Job is completed and cannot be changed."));
	return;
      }
      else if (con->response->request.status.status_code == IPP_OK)
      {
        cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-priority to %d",
	            attr->values[0].integer);
        cupsdSetJobPriority(job, attr->values[0].integer);

	check_jobs = 1;
        event      |= CUPSD_EVENT_JOB_CONFIG_CHANGED |
	              CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED;
      }
    }
    else if (!strcmp(attr->name, "job-state"))
    {
     /*
      * Change the job state...
      */

      if (attr->value_tag != IPP_TAG_ENUM)
      {
	send_ipp_status(con, IPP_REQUEST_VALUE, _("Bad job-state value."));

	attr2 = ippCopyAttribute(con->response, attr, 0);
	ippSetGroupTag(con->response, &attr2, IPP_TAG_UNSUPPORTED_GROUP);
      }
      else
      {
        switch (attr->values[0].integer)
	{
	  case IPP_JOB_PENDING :
	  case IPP_JOB_HELD :
	      if (job->state_value > IPP_JOB_HELD)
	      {
		send_ipp_status(con, IPP_NOT_POSSIBLE,
		                _("Job state cannot be changed."));
		return;
	      }
              else if (con->response->request.status.status_code == IPP_OK)
	      {
		cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-state to %d",
			    attr->values[0].integer);
                cupsdSetJobState(job, (ipp_jstate_t)attr->values[0].integer, CUPSD_JOB_DEFAULT, "Job state changed by \"%s\"", username);
		check_jobs = 1;
	      }
	      break;

	  case IPP_JOB_PROCESSING :
	  case IPP_JOB_STOPPED :
	      if (job->state_value != attr->values[0].integer)
	      {
		send_ipp_status(con, IPP_NOT_POSSIBLE,
		                _("Job state cannot be changed."));
		return;
	      }
	      break;

	  case IPP_JOB_CANCELED :
	  case IPP_JOB_ABORTED :
	  case IPP_JOB_COMPLETED :
	      if (job->state_value > IPP_JOB_PROCESSING)
	      {
		send_ipp_status(con, IPP_NOT_POSSIBLE,
		                _("Job state cannot be changed."));
		return;
	      }
              else if (con->response->request.status.status_code == IPP_OK)
	      {
		cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-state to %d",
			    attr->values[0].integer);
                cupsdSetJobState(job, (ipp_jstate_t)attr->values[0].integer,
		                 CUPSD_JOB_DEFAULT,
				 "Job state changed by \"%s\"", username);
                check_jobs = 1;
	      }
	      break;
	}
      }
    }
    else if (con->response->request.status.status_code != IPP_OK)
      continue;
    else if ((attr2 = ippFindAttribute(job->attrs, attr->name,
                                       IPP_TAG_ZERO)) != NULL)
    {
     /*
      * Some other value; first free the old value...
      */

      if (job->attrs->prev)
        job->attrs->prev->next = attr2->next;
      else
        job->attrs->attrs = attr2->next;

      if (job->attrs->last == attr2)
        job->attrs->last = job->attrs->prev;

      ippDeleteAttribute(NULL, attr2);

     /*
      * Then copy the attribute...
      */

      ippCopyAttribute(job->attrs, attr, 0);

     /*
      * See if the job-name or job-hold-until is being changed.
      */

      if (!strcmp(attr->name, "job-hold-until"))
      {
        cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-hold-until to %s",
		    attr->values[0].string.text);
        cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);

	if (!strcmp(attr->values[0].string.text, "no-hold"))
	{
	  cupsdReleaseJob(job);
          check_jobs = 1;
	}
	else
	  cupsdSetJobState(job, IPP_JOB_HELD, CUPSD_JOB_DEFAULT,
	                   "Job held by \"%s\".", username);

        event |= CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE;
      }
    }
    else if (attr->value_tag == IPP_TAG_DELETEATTR)
    {
     /*
      * Delete the attribute...
      */

      if ((attr2 = ippFindAttribute(job->attrs, attr->name,
                                    IPP_TAG_ZERO)) != NULL)
      {
        if (job->attrs->prev)
	  job->attrs->prev->next = attr2->next;
	else
	  job->attrs->attrs = attr2->next;

        if (attr2 == job->attrs->last)
	  job->attrs->last = job->attrs->prev;

        ippDeleteAttribute(NULL, attr2);

        event |= CUPSD_EVENT_JOB_CONFIG_CHANGED;
      }
    }
    else
    {
     /*
      * Add new option by copying it...
      */

      ippCopyAttribute(job->attrs, attr, 0);

      event |= CUPSD_EVENT_JOB_CONFIG_CHANGED;
    }
  }

 /*
  * Save the job...
  */

  job->dirty = 1;
  cupsdMarkDirty(CUPSD_DIRTY_JOBS);

 /*
  * Send events as needed...
  */

  if (event & CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED)
    cupsdAddEvent(CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED,
                  cupsdFindDest(job->dest), job,
                  "Job priority changed by user.");

  if (event & CUPSD_EVENT_JOB_STATE)
    cupsdAddEvent(CUPSD_EVENT_JOB_STATE, cupsdFindDest(job->dest), job,
                  job->state_value == IPP_JOB_HELD ?
		      "Job held by user." : "Job restarted by user.");

  if (event & CUPSD_EVENT_JOB_CONFIG_CHANGED)
    cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, cupsdFindDest(job->dest), job,
                  "Job options changed by user.");

 /*
  * Start jobs if possible...
  */

  if (check_jobs)
    cupsdCheckJobs();
}