get_jobs(cupsd_client_t  *con,		/* I - Client connection */
	 ipp_attribute_t *uri)		/* I - Printer URI */
{
  http_status_t	status;			/* Policy status */
  ipp_attribute_t *attr;		/* Current attribute */
  const char	*dest;			/* Destination */
  cups_ptype_t	dtype;			/* Destination type (printer/class) */
  cups_ptype_t	dmask;			/* Destination type mask */
  char		scheme[HTTP_MAX_URI],	/* Scheme 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		job_comparison;		/* Job comparison */
  ipp_jstate_t	job_state;		/* job-state value */
  int		first_job_id = 1,	/* First job ID */
		first_index = 1,	/* First index */
		limit = 0,		/* Maximum number of jobs to return */
		count,			/* Number of jobs that match */
		need_load_job = 0;	/* Do we need to load the job? */
  const char	*job_attr;		/* Job attribute requested */
  ipp_attribute_t *job_ids;		/* job-ids attribute */
  cupsd_job_t	*job;			/* Current job pointer */
  cupsd_printer_t *printer;		/* Printer */
  cups_array_t	*list;			/* Which job list... */
  int		delete_list = 0;	/* Delete the list afterwards? */
  cups_array_t	*ra,			/* Requested attributes array */
		*exclude;		/* Private attributes array */
  cupsd_policy_t *policy;		/* Current policy */


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

 /*
  * Is the destination valid?
  */

  if (strcmp(uri->name, "printer-uri"))
  {
    send_ipp_status(con, IPP_BAD_REQUEST, _("No printer-uri in request."));
    return;
  }

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

  if (!strcmp(resource, "/") || !strcmp(resource, "/jobs"))
  {
    dest    = NULL;
    dtype   = (cups_ptype_t)0;
    dmask   = (cups_ptype_t)0;
    printer = NULL;
  }
  else if (!strncmp(resource, "/printers", 9) && strlen(resource) <= 10)
  {
    dest    = NULL;
    dtype   = (cups_ptype_t)0;
    dmask   = CUPS_PRINTER_CLASS;
    printer = NULL;
  }
  else if (!strncmp(resource, "/classes", 8) && strlen(resource) <= 9)
  {
    dest    = NULL;
    dtype   = CUPS_PRINTER_CLASS;
    dmask   = CUPS_PRINTER_CLASS;
    printer = NULL;
  }
  else if ((dest = cupsdValidateDest(uri->values[0].string.text, &dtype,
                                     &printer)) == NULL)
  {
   /*
    * Bad URI...
    */

    send_ipp_status(con, IPP_NOT_FOUND,
                    _("The printer or class does not exist."));
    return;
  }
  else
  {
    dtype &= CUPS_PRINTER_CLASS;
    dmask = CUPS_PRINTER_CLASS;
  }

 /*
  * Check policy...
  */

  if (printer)
    policy = printer->op_policy_ptr;
  else
    policy = DefaultPolicyPtr;

  if ((status = cupsdCheckPolicy(policy, con, NULL)) != HTTP_OK)
  {
    send_http_error(con, status, NULL);
    return;
  }

  job_ids = ippFindAttribute(con->request, "job-ids", IPP_TAG_INTEGER);

 /*
  * See if the "which-jobs" attribute have been specified...
  */

  if ((attr = ippFindAttribute(con->request, "which-jobs",
                               IPP_TAG_KEYWORD)) != NULL && job_ids)
  {
    send_ipp_status(con, IPP_CONFLICT,
                    _("The %s attribute cannot be provided with job-ids."),
                    "which-jobs");
    return;
  }
  else if (!attr || !strcmp(attr->values[0].string.text, "not-completed"))
  {
    job_comparison = -1;
    job_state      = IPP_JOB_STOPPED;
    list           = ActiveJobs;
  }
  else if (!strcmp(attr->values[0].string.text, "completed"))
  {
    job_comparison = 1;
    job_state      = IPP_JOB_CANCELED;
    list           = cupsdGetCompletedJobs(printer);
    delete_list    = 1;
  }
  else if (!strcmp(attr->values[0].string.text, "aborted"))
  {
    job_comparison = 0;
    job_state      = IPP_JOB_ABORTED;
    list           = cupsdGetCompletedJobs(printer);
    delete_list    = 1;
  }
  else if (!strcmp(attr->values[0].string.text, "all"))
  {
    job_comparison = 1;
    job_state      = IPP_JOB_PENDING;
    list           = Jobs;
  }
  else if (!strcmp(attr->values[0].string.text, "canceled"))
  {
    job_comparison = 0;
    job_state      = IPP_JOB_CANCELED;
    list           = cupsdGetCompletedJobs(printer);
    delete_list    = 1;
  }
  else if (!strcmp(attr->values[0].string.text, "pending"))
  {
    job_comparison = 0;
    job_state      = IPP_JOB_PENDING;
    list           = ActiveJobs;
  }
  else if (!strcmp(attr->values[0].string.text, "pending-held"))
  {
    job_comparison = 0;
    job_state      = IPP_JOB_HELD;
    list           = ActiveJobs;
  }
  else if (!strcmp(attr->values[0].string.text, "processing"))
  {
    job_comparison = 0;
    job_state      = IPP_JOB_PROCESSING;
    list           = PrintingJobs;
  }
  else if (!strcmp(attr->values[0].string.text, "processing-stopped"))
  {
    job_comparison = 0;
    job_state      = IPP_JOB_STOPPED;
    list           = ActiveJobs;
  }
  else
  {
    send_ipp_status(con, IPP_ATTRIBUTES,
                    _("The which-jobs value \"%s\" is not supported."),
		    attr->values[0].string.text);
    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
                 "which-jobs", NULL, attr->values[0].string.text);
    return;
  }

 /*
  * See if they want to limit the number of jobs reported...
  */

  if ((attr = ippFindAttribute(con->request, "limit", IPP_TAG_INTEGER)) != NULL)
  {
    if (job_ids)
    {
      send_ipp_status(con, IPP_CONFLICT,
		      _("The %s attribute cannot be provided with job-ids."),
		      "limit");
      return;
    }

    limit = attr->values[0].integer;
  }

  if ((attr = ippFindAttribute(con->request, "first-index", IPP_TAG_INTEGER)) != NULL)
  {
    if (job_ids)
    {
      send_ipp_status(con, IPP_CONFLICT,
		      _("The %s attribute cannot be provided with job-ids."),
		      "first-index");
      return;
    }

    first_index = attr->values[0].integer;
  }
  else if ((attr = ippFindAttribute(con->request, "first-job-id", IPP_TAG_INTEGER)) != NULL)
  {
    if (job_ids)
    {
      send_ipp_status(con, IPP_CONFLICT,
		      _("The %s attribute cannot be provided with job-ids."),
		      "first-job-id");
      return;
    }

    first_job_id = attr->values[0].integer;
  }

 /*
  * See if we only want to see jobs for a specific user...
  */

  if ((attr = ippFindAttribute(con->request, "my-jobs", IPP_TAG_BOOLEAN)) != NULL && job_ids)
  {
    send_ipp_status(con, IPP_CONFLICT,
                    _("The %s attribute cannot be provided with job-ids."),
                    "my-jobs");
    return;
  }
  else if (attr && attr->values[0].boolean)
    strlcpy(username, get_username(con), sizeof(username));
  else
    username[0] = '\0';

  ra = create_requested_array(con->request);
  for (job_attr = (char *)cupsArrayFirst(ra); job_attr; job_attr = (char *)cupsArrayNext(ra))
    if (strcmp(job_attr, "job-id") &&
	strcmp(job_attr, "job-k-octets") &&
	strcmp(job_attr, "job-media-progress") &&
	strcmp(job_attr, "job-more-info") &&
	strcmp(job_attr, "job-name") &&
	strcmp(job_attr, "job-originating-user-name") &&
	strcmp(job_attr, "job-preserved") &&
	strcmp(job_attr, "job-printer-up-time") &&
        strcmp(job_attr, "job-printer-uri") &&
	strcmp(job_attr, "job-state") &&
	strcmp(job_attr, "job-state-reasons") &&
	strcmp(job_attr, "job-uri") &&
	strcmp(job_attr, "time-at-completed") &&
	strcmp(job_attr, "time-at-creation") &&
	strcmp(job_attr, "number-of-documents"))
    {
      need_load_job = 1;
      break;
    }

  if (need_load_job && (limit == 0 || limit > 500) && (list == Jobs || delete_list))
  {
   /*
    * Limit expensive Get-Jobs for job history to 500 jobs...
    */

    ippAddInteger(con->response, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "limit", 500);

    if (limit)
      ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER, "limit", limit);

    limit = 500;

    cupsdLogClient(con, CUPSD_LOG_INFO, "Limiting Get-Jobs response to %d jobs.", limit);
  }

 /*
  * OK, build a list of jobs for this printer...
  */

  if (job_ids)
  {
    int	i;				/* Looping var */

    for (i = 0; i < job_ids->num_values; i ++)
    {
      if (!cupsdFindJob(job_ids->values[i].integer))
        break;
    }

    if (i < job_ids->num_values)
    {
      send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist."),
                      job_ids->values[i].integer);
      return;
    }

    for (i = 0; i < job_ids->num_values; i ++)
    {
      job = cupsdFindJob(job_ids->values[i].integer);

      if (need_load_job && !job->attrs)
      {
        cupsdLoadJob(job);

	if (!job->attrs)
	{
	  cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d", job->id);
	  continue;
	}
      }

      if (i > 0)
	ippAddSeparator(con->response);

      exclude = cupsdGetPrivateAttrs(job->printer ?
                                         job->printer->op_policy_ptr :
					 policy, con, job->printer,
					 job->username);

      copy_job_attrs(con, job, ra, exclude);
    }
  }
  else
  {
    if (first_index > 1)
      job = (cupsd_job_t *)cupsArrayIndex(list, first_index - 1);
    else
      job = (cupsd_job_t *)cupsArrayFirst(list);

    for (count = 0; (limit <= 0 || count < limit) && job; job = (cupsd_job_t *)cupsArrayNext(list))
    {
     /*
      * Filter out jobs that don't match...
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG2,
		      "get_jobs: job->id=%d, dest=\"%s\", username=\"%s\", "
		      "state_value=%d, attrs=%p", job->id, job->dest,
		      job->username, job->state_value, job->attrs);

      if (!job->dest || !job->username)
	cupsdLoadJob(job);

      if (!job->dest || !job->username)
	continue;

      if ((dest && strcmp(job->dest, dest)) &&
	  (!job->printer || !dest || strcmp(job->printer->name, dest)))
	continue;
      if ((job->dtype & dmask) != dtype &&
	  (!job->printer || (job->printer->type & dmask) != dtype))
	continue;

      if ((job_comparison < 0 && job->state_value > job_state) ||
          (job_comparison == 0 && job->state_value != job_state) ||
          (job_comparison > 0 && job->state_value < job_state))
	continue;

      if (job->id < first_job_id)
	continue;

      if (need_load_job && !job->attrs)
      {
        cupsdLoadJob(job);

	if (!job->attrs)
	{
	  cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d", job->id);
	  continue;
	}
      }

      if (username[0] && _cups_strcasecmp(username, job->username))
	continue;

      if (count > 0)
	ippAddSeparator(con->response);

      count ++;

      exclude = cupsdGetPrivateAttrs(job->printer ?
                                         job->printer->op_policy_ptr :
					 policy, con, job->printer,
					 job->username);

      copy_job_attrs(con, job, ra, exclude);
    }

    cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count=%d", count);
  }

  cupsArrayDelete(ra);

  if (delete_list)
    cupsArrayDelete(list);

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