restart_job(cupsd_client_t  *con,	/* I - Client connection */
            ipp_attribute_t *uri)	/* I - Job or Printer URI */
{
  ipp_attribute_t *attr;		/* Current attribute */
  int		jobid;			/* Job ID */
  cupsd_job_t	*job;			/* Job information */
  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 */


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

 /*
  * 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 job is in any of the "completed" states...
  */

  if (job->state_value <= IPP_JOB_PROCESSING)
  {
   /*
    * Nope - return a "not possible" error...
    */

    send_ipp_status(con, IPP_NOT_POSSIBLE, _("Job #%d is not complete."),
                    jobid);
    return;
  }

 /*
  * See if we have retained the job files...
  */

  cupsdLoadJob(job);

  if (!job->attrs || job->num_files == 0)
  {
   /*
    * Nope - return a "not possible" error...
    */

    send_ipp_status(con, IPP_NOT_POSSIBLE,
                    _("Job #%d cannot be restarted - no files."), 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 if the job-hold-until attribute is specified...
  */

  if ((attr = ippFindAttribute(con->request, "job-hold-until",
                               IPP_TAG_KEYWORD)) == NULL)
    attr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME);

  if (attr && strcmp(attr->values[0].string.text, "no-hold"))
  {
   /*
    * Return the job to a held state...
    */

    cupsdLogJob(job, CUPSD_LOG_DEBUG,
		"Restarted by \"%s\" with job-hold-until=%s.",
                username, attr->values[0].string.text);
    cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);

    cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE,
                  NULL, job, "Job restarted by user with job-hold-until=%s",
		  attr->values[0].string.text);
  }
  else
  {
   /*
    * Restart the job...
    */

    cupsdRestartJob(job);
    cupsdCheckJobs();
  }

  cupsdLogJob(job, CUPSD_LOG_INFO, "Restarted by \"%s\".", username);

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