]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/dest-options.c
Fix source file header text duplication text duplication.
[thirdparty/cups.git] / cups / dest-options.c
index a36b5334e8dce3ec574550a5faf69cacc544011c..bf9020bac6e31eb0fb0b86fca5674ead69961fa7 100644 (file)
@@ -1,37 +1,15 @@
 /*
- * "$Id$"
+ * Destination option/media support for CUPS.
  *
- *   Destination option/media support for CUPS.
+ * Copyright 2012-2016 by Apple Inc.
  *
- *   Copyright 2012 by Apple Inc.
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file.  If this file is
+ * missing or damaged, see the license at "http://www.cups.org/".
  *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
-*   cupsCheckDestSupported() - Check that the option and value are supported
- *                             by the destination.
- *   cupsCopyDestConflicts()  - Get conflicts and resolutions for a new
- *                             option/value pair.
- *   cupsCopyDestInfo()       - Get the supported values/capabilities for the
- *                             destination.
- *   cupsFreeDestInfo()       - Free destination information obtained using
- *                             @link cupsCopyDestInfo@.
- *   cupsGetDestMediaByName() - Get media names, dimensions, and margins.
- *   cupsGetDestMediaBySize() - Get media names, dimensions, and margins.
- *   cups_compare_media_db()  - Compare two media entries.
- *   cups_copy_media_db()     - Copy a media entry.
- *   cups_create_media_db()   - Create the media database.
- *   cups_free_media_cb()     - Free a media entry.
- *   cups_get_media_db()      - Lookup the media entry for a given size.
- *   cups_is_close_media_db() - Compare two media entries to see if they are
- *                             close to the same size.
+ * This file is subject to the Apple OS-Developed Software exception.
  */
 
 /*
 #include "cups-private.h"
 
 
+/*
+ * Local constants...
+ */
+
+#define _CUPS_MEDIA_READY_TTL  30      /* Life of xxx-ready values */
+
+
 /*
  * Local functions...
  */
 
+static void            cups_add_dconstres(cups_array_t *a, ipp_t *collection);
+static int             cups_compare_dconstres(_cups_dconstres_t *a,
+                                              _cups_dconstres_t *b);
 static int             cups_compare_media_db(_cups_media_db_t *a,
                                              _cups_media_db_t *b);
 static _cups_media_db_t        *cups_copy_media_db(_cups_media_db_t *mdb);
-static void            cups_create_media_db(cups_dinfo_t *dinfo);
+static void            cups_create_cached(http_t *http, cups_dinfo_t *dinfo,
+                                          unsigned flags);
+static void            cups_create_constraints(cups_dinfo_t *dinfo);
+static void            cups_create_defaults(cups_dinfo_t *dinfo);
+static void            cups_create_media_db(cups_dinfo_t *dinfo,
+                                            unsigned flags);
 static void            cups_free_media_db(_cups_media_db_t *mdb);
-static int             cups_get_media_db(cups_dinfo_t *dinfo,
-                                         _pwg_media_t *pwg, unsigned flags,
+static int             cups_get_media_db(http_t *http, cups_dinfo_t *dinfo,
+                                         pwg_media_t *pwg, unsigned flags,
                                          cups_size_t *size);
 static int             cups_is_close_media_db(_cups_media_db_t *a,
                                               _cups_media_db_t *b);
+static cups_array_t    *cups_test_constraints(cups_dinfo_t *dinfo,
+                                              const char *new_option,
+                                              const char *new_value,
+                                              int num_options,
+                                              cups_option_t *options,
+                                              int *num_conflicts,
+                                              cups_option_t **conflicts);
+static void            cups_update_ready(http_t *http, cups_dinfo_t *dinfo);
+
 
 /*
  * 'cupsCheckDestSupported()' - Check that the option and value are supported
@@ -62,7 +64,7 @@ static int            cups_is_close_media_db(_cups_media_db_t *a,
  *
  * Returns 1 if supported, 0 otherwise.
  *
- * @since CUPS 1.6@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 int                                    /* O - 1 if supported, 0 otherwise */
@@ -115,7 +117,7 @@ cupsCheckDestSupported(
     * Check range of custom media sizes...
     */
 
-    _pwg_media_t       *pwg;           /* Current PWG media size info */
+    pwg_media_t        *pwg;           /* Current PWG media size info */
     int                        min_width,      /* Minimum width */
                        min_length,     /* Minimum length */
                        max_width,      /* Maximum width */
@@ -133,13 +135,13 @@ cupsCheckDestSupported(
         i --, attrval ++)
     {
       if (!strncmp(attrval->string.text, "custom_min_", 11) &&
-          (pwg = _pwgMediaForPWG(attrval->string.text)) != NULL)
+          (pwg = pwgMediaForPWG(attrval->string.text)) != NULL)
       {
         min_width  = pwg->width;
         min_length = pwg->length;
       }
       else if (!strncmp(attrval->string.text, "custom_max_", 11) &&
-              (pwg = _pwgMediaForPWG(attrval->string.text)) != NULL)
+              (pwg = pwgMediaForPWG(attrval->string.text)) != NULL)
       {
         max_width  = pwg->width;
         max_length = pwg->length;
@@ -151,7 +153,7 @@ cupsCheckDestSupported(
     */
 
     if (min_width < INT_MAX && max_width > 0 &&
-        (pwg = _pwgMediaForPWG(value)) != NULL &&
+        (pwg = pwgMediaForPWG(value)) != NULL &&
         pwg->width >= min_width && pwg->width <= max_width &&
         pwg->length >= min_length && pwg->length <= max_length)
       return (1);
@@ -176,6 +178,15 @@ cupsCheckDestSupported(
       case IPP_TAG_BOOLEAN :
           return (attr->values[0].boolean);
 
+      case IPP_TAG_RANGE :
+          int_value = atoi(value);
+
+          for (i = 0; i < attr->num_values; i ++)
+            if (int_value >= attr->values[i].range.lower &&
+                int_value <= attr->values[i].range.upper)
+              return (1);
+          break;
+
       case IPP_TAG_RESOLUTION :
           if (sscanf(value, "%dx%d%15s", &xres_value, &yres_value, temp) != 3)
           {
@@ -239,20 +250,21 @@ cupsCheckDestSupported(
  * user.  "new_option" and "new_value" are the setting the user has just
  * changed.
  *
- * Returns 1 if there is a conflict and 0 otherwise.
+ * Returns 1 if there is a conflict, 0 if there are no conflicts, and -1 if
+ * there was an unrecoverable error such as a resolver loop.
  *
- * If "num_conflicts" and "conflicts" are not NULL, they are set to contain the
- * list of conflicting option/value pairs.  Similarly, if "num_resolved" and
- * "resolved" are not NULL they will be set to the list of changes needed to
- * resolve the conflict.
+ * If "num_conflicts" and "conflicts" are not @code NULL@, they are set to
+ * contain the list of conflicting option/value pairs.  Similarly, if
+ * "num_resolved" and "resolved" are not @code NULL@ they will be set to the
+ * list of changes needed to resolve the conflict.
  *
  * If cupsCopyDestConflicts returns 1 but "num_resolved" and "resolved" are set
- * to 0 and NULL, respectively, then the conflict cannot be resolved.
+ * to 0 and @code NULL@, respectively, then the conflict cannot be resolved.
  *
- * @since CUPS 1.6@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
-int                                    /* O - 1 if there is a conflict */
+int                                    /* O - 1 if there is a conflict, 0 if none, -1 on error */
 cupsCopyDestConflicts(
     http_t        *http,               /* I - Connection to destination */
     cups_dest_t   *dest,               /* I - Destination */
@@ -266,6 +278,27 @@ cupsCopyDestConflicts(
     int           *num_resolved,       /* O - Number of options to resolve */
     cups_option_t **resolved)          /* O - Resolved options */
 {
+  int          i,                      /* Looping var */
+               have_conflicts = 0,     /* Do we have conflicts? */
+               changed,                /* Did we change something? */
+               tries,                  /* Number of tries for resolution */
+               num_myconf = 0,         /* My number of conflicting options */
+               num_myres = 0;          /* My number of resolved options */
+  cups_option_t        *myconf = NULL,         /* My conflicting options */
+               *myres = NULL,          /* My resolved options */
+               *myoption,              /* My current option */
+               *option;                /* Current option */
+  cups_array_t *active = NULL,         /* Active conflicts */
+               *pass = NULL,           /* Resolvers for this pass */
+               *resolvers = NULL,      /* Resolvers we have used */
+               *test;                  /* Test array for conflicts */
+  _cups_dconstres_t *c,                        /* Current constraint */
+               *r;                     /* Current resolver */
+  ipp_attribute_t *attr;               /* Current attribute */
+  char         value[2048];            /* Current attribute value as string */
+  const char   *myvalue;               /* Current value of an option */
+
+
  /*
   * Clear returned values...
   */
@@ -286,18 +319,223 @@ cupsCopyDestConflicts(
   * Range check input...
   */
 
-  if (!http || !dest || !dinfo || !new_option || !new_value ||
+  if (!http || !dest || !dinfo ||
       (num_conflicts != NULL) != (conflicts != NULL) ||
       (num_resolved != NULL) != (resolved != NULL))
     return (0);
 
  /*
-  * Check for and resolve any conflicts...
+  * Load constraints as needed...
   */
 
-  /* TODO: implement me! */
+  if (!dinfo->constraints)
+    cups_create_constraints(dinfo);
 
-  return (0);
+  if (cupsArrayCount(dinfo->constraints) == 0)
+    return (0);
+
+  if (!dinfo->num_defaults)
+    cups_create_defaults(dinfo);
+
+ /*
+  * If we are resolving, create a shadow array...
+  */
+
+  if (num_resolved)
+  {
+    for (i = num_options, option = options; i > 0; i --, option ++)
+      num_myres = cupsAddOption(option->name, option->value, num_myres, &myres);
+
+    if (new_option && new_value)
+      num_myres = cupsAddOption(new_option, new_value, num_myres, &myres);
+  }
+  else
+  {
+    num_myres = num_options;
+    myres     = options;
+  }
+
+ /*
+  * Check for any conflicts...
+  */
+
+  if (num_resolved)
+    pass = cupsArrayNew((cups_array_func_t)cups_compare_dconstres, NULL);
+
+  for (tries = 0; tries < 100; tries ++)
+  {
+   /*
+    * Check for any conflicts...
+    */
+
+    if (num_conflicts || num_resolved)
+    {
+      cupsFreeOptions(num_myconf, myconf);
+
+      num_myconf = 0;
+      myconf     = NULL;
+      active     = cups_test_constraints(dinfo, new_option, new_value,
+                                         num_myres, myres, &num_myconf,
+                                         &myconf);
+    }
+    else
+      active = cups_test_constraints(dinfo, new_option, new_value, num_myres,
+                                    myres, NULL, NULL);
+
+    have_conflicts = (active != NULL);
+
+    if (!active || !num_resolved)
+      break;                           /* All done */
+
+   /*
+    * Scan the constraints that were triggered to apply resolvers...
+    */
+
+    if (!resolvers)
+      resolvers = cupsArrayNew((cups_array_func_t)cups_compare_dconstres, NULL);
+
+    for (c = (_cups_dconstres_t *)cupsArrayFirst(active), changed = 0;
+         c;
+         c = (_cups_dconstres_t *)cupsArrayNext(active))
+    {
+      if (cupsArrayFind(pass, c))
+        continue;                      /* Already applied this resolver... */
+
+      if (cupsArrayFind(resolvers, c))
+      {
+        DEBUG_printf(("1cupsCopyDestConflicts: Resolver loop with %s.",
+                      c->name));
+        have_conflicts = -1;
+        goto cleanup;
+      }
+
+      if ((r = cupsArrayFind(dinfo->resolvers, c)) == NULL)
+      {
+        DEBUG_printf(("1cupsCopyDestConflicts: Resolver %s not found.",
+                      c->name));
+        have_conflicts = -1;
+        goto cleanup;
+      }
+
+     /*
+      * Add the options from the resolver...
+      */
+
+      cupsArrayAdd(pass, r);
+      cupsArrayAdd(resolvers, r);
+
+      for (attr = ippFirstAttribute(r->collection);
+           attr;
+           attr = ippNextAttribute(r->collection))
+      {
+        if (new_option && !strcmp(attr->name, new_option))
+          continue;                    /* Ignore this if we just changed it */
+
+        if (ippAttributeString(attr, value, sizeof(value)) >= sizeof(value))
+          continue;                    /* Ignore if the value is too long */
+
+        if ((test = cups_test_constraints(dinfo, attr->name, value, num_myres,
+                                          myres, NULL, NULL)) == NULL)
+        {
+         /*
+          * That worked, flag it...
+          */
+
+          changed = 1;
+        }
+        else
+          cupsArrayDelete(test);
+
+       /*
+       * Add the option/value from the resolver regardless of whether it
+       * worked; this makes sure that we can cascade several changes to
+       * make things resolve...
+       */
+
+       num_myres = cupsAddOption(attr->name, value, num_myres, &myres);
+      }
+    }
+
+    if (!changed)
+    {
+      DEBUG_puts("1cupsCopyDestConflicts: Unable to resolve constraints.");
+      have_conflicts = -1;
+      goto cleanup;
+    }
+
+    cupsArrayClear(pass);
+
+    cupsArrayDelete(active);
+    active = NULL;
+  }
+
+  if (tries >= 100)
+  {
+    DEBUG_puts("1cupsCopyDestConflicts: Unable to resolve after 100 tries.");
+    have_conflicts = -1;
+    goto cleanup;
+  }
+
+ /*
+  * Copy resolved options as needed...
+  */
+
+  if (num_resolved)
+  {
+    for (i = num_myres, myoption = myres; i > 0; i --, myoption ++)
+    {
+      if ((myvalue = cupsGetOption(myoption->name, num_options,
+                                   options)) == NULL ||
+          strcmp(myvalue, myoption->value))
+      {
+        if (new_option && !strcmp(new_option, myoption->name) &&
+            new_value && !strcmp(new_value, myoption->value))
+          continue;
+
+        *num_resolved = cupsAddOption(myoption->name, myoption->value,
+                                      *num_resolved, resolved);
+      }
+    }
+  }
+
+ /*
+  * Clean up...
+  */
+
+  cleanup:
+
+  cupsArrayDelete(active);
+  cupsArrayDelete(pass);
+  cupsArrayDelete(resolvers);
+
+  if (num_resolved)
+  {
+   /*
+    * Free shadow copy of options...
+    */
+
+    cupsFreeOptions(num_myres, myres);
+  }
+
+  if (num_conflicts)
+  {
+   /*
+    * Return conflicting options to caller...
+    */
+
+    *num_conflicts = num_myconf;
+    *conflicts     = myconf;
+  }
+  else
+  {
+   /*
+    * Free conflicting options...
+    */
+
+    cupsFreeOptions(num_myconf, myconf);
+  }
+
+  return (have_conflicts);
 }
 
 
@@ -308,7 +546,7 @@ cupsCopyDestConflicts(
  * The caller is responsible for calling @link cupsFreeDestInfo@ on the return
  * value. @code NULL@ is returned on error.
  *
- * @since CUPS 1.6@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 cups_dinfo_t *                         /* O - Destination information */
@@ -319,6 +557,9 @@ cupsCopyDestInfo(
   cups_dinfo_t *dinfo;                 /* Destination information */
   ipp_t                *request,               /* Get-Printer-Attributes request */
                *response;              /* Supported attributes */
+  int          tries,                  /* Number of tries so far */
+               delay,                  /* Current retry delay */
+               prev_delay;             /* Next retry delay */
   const char   *uri;                   /* Printer URI */
   char         resource[1024];         /* Resource path */
   int          version;                /* IPP version */
@@ -331,8 +572,7 @@ cupsCopyDestInfo(
   };
 
 
-  DEBUG_printf(("cupsCopyDestSupported(http=%p, dest=%p(%s))", http, dest,
-                dest ? dest->name : ""));
+  DEBUG_printf(("cupsCopyDestSupported(http=%p, dest=%p(%s))", (void *)http, (void *)dest, dest ? dest->name : ""));
 
  /*
   * Range check input...
@@ -352,7 +592,10 @@ cupsCopyDestInfo(
   * Get the supported attributes...
   */
 
-  version = 20;
+  delay      = 1;
+  prev_delay = 1;
+  tries      = 0;
+  version    = 20;
 
   do
   {
@@ -360,11 +603,11 @@ cupsCopyDestInfo(
     * Send a Get-Printer-Attributes request...
     */
 
-    request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
+    request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
                 uri);
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
-                NULL, cupsUser());
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                 "requesting-user-name", NULL, cupsUser());
     ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
                  "requested-attributes",
                  (int)(sizeof(requested_attrs) / sizeof(requested_attrs[0])),
@@ -372,7 +615,7 @@ cupsCopyDestInfo(
     response = cupsDoRequest(http, request, resource);
     status   = cupsLastError();
 
-    if (status > IPP_OK_SUBST)
+    if (status > IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED)
     {
       DEBUG_printf(("cupsCopyDestSupported: Get-Printer-Attributes for '%s' "
                    "returned %s (%s)", dest->name, ippErrorString(status),
@@ -381,13 +624,24 @@ cupsCopyDestInfo(
       ippDelete(response);
       response = NULL;
 
-      if (status == IPP_VERSION_NOT_SUPPORTED && version > 11)
+      if (status == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED && version > 11)
         version = 11;
+      else if (status == IPP_STATUS_ERROR_BUSY)
+      {
+        sleep((unsigned)delay);
+
+        delay = _cupsNextDelay(delay, &prev_delay);
+      }
       else
         return (NULL);
     }
+
+    tries ++;
   }
-  while (!response);
+  while (!response && tries < 10);
+
+  if (!response)
+    return (NULL);
 
  /*
   * Allocate a cups_dinfo_t structure and return it...
@@ -395,11 +649,12 @@ cupsCopyDestInfo(
 
   if ((dinfo = calloc(1, sizeof(cups_dinfo_t))) == NULL)
   {
-    _cupsSetError(IPP_INTERNAL_ERROR, strerror(errno), 0);
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
     ippDelete(response);
     return (NULL);
   }
 
+  dinfo->version  = version;
   dinfo->uri      = uri;
   dinfo->resource = _cupsStrAlloc(resource);
   dinfo->attrs    = response;
@@ -408,6 +663,131 @@ cupsCopyDestInfo(
 }
 
 
+/*
+ * 'cupsFindDestDefault()' - Find the default value(s) for the given option.
+ *
+ * The returned value is an IPP attribute. Use the @code ippGetBoolean@,
+ * @code ippGetCollection@, @code ippGetCount@, @code ippGetDate@,
+ * @code ippGetInteger@, @code ippGetOctetString@, @code ippGetRange@,
+ * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
+ * functions to inspect the default value(s) as needed.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+ipp_attribute_t        *                       /* O - Default attribute or @code NULL@ for none */
+cupsFindDestDefault(
+    http_t       *http,                        /* I - Connection to destination */
+    cups_dest_t  *dest,                        /* I - Destination */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    const char   *option)              /* I - Option/attribute name */
+{
+  char name[IPP_MAX_NAME];             /* Attribute name */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (!http || !dest || !dinfo || !option)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (NULL);
+  }
+
+ /*
+  * Find and return the attribute...
+  */
+
+  snprintf(name, sizeof(name), "%s-default", option);
+  return (ippFindAttribute(dinfo->attrs, name, IPP_TAG_ZERO));
+}
+
+
+/*
+ * 'cupsFindDestReady()' - Find the default value(s) for the given option.
+ *
+ * The returned value is an IPP attribute. Use the @code ippGetBoolean@,
+ * @code ippGetCollection@, @code ippGetCount@, @code ippGetDate@,
+ * @code ippGetInteger@, @code ippGetOctetString@, @code ippGetRange@,
+ * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
+ * functions to inspect the default value(s) as needed.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+ipp_attribute_t        *                       /* O - Default attribute or @code NULL@ for none */
+cupsFindDestReady(
+    http_t       *http,                        /* I - Connection to destination */
+    cups_dest_t  *dest,                        /* I - Destination */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    const char   *option)              /* I - Option/attribute name */
+{
+  char name[IPP_MAX_NAME];             /* Attribute name */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (!http || !dest || !dinfo || !option)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (NULL);
+  }
+
+ /*
+  * Find and return the attribute...
+  */
+
+  cups_update_ready(http, dinfo);
+
+  snprintf(name, sizeof(name), "%s-ready", option);
+  return (ippFindAttribute(dinfo->ready_attrs, name, IPP_TAG_ZERO));
+}
+
+
+/*
+ * 'cupsFindDestSupported()' - Find the default value(s) for the given option.
+ *
+ * The returned value is an IPP attribute. Use the @code ippGetBoolean@,
+ * @code ippGetCollection@, @code ippGetCount@, @code ippGetDate@,
+ * @code ippGetInteger@, @code ippGetOctetString@, @code ippGetRange@,
+ * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
+ * functions to inspect the default value(s) as needed.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+ipp_attribute_t        *                       /* O - Default attribute or @code NULL@ for none */
+cupsFindDestSupported(
+    http_t       *http,                        /* I - Connection to destination */
+    cups_dest_t  *dest,                        /* I - Destination */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    const char   *option)              /* I - Option/attribute name */
+{
+  char name[IPP_MAX_NAME];             /* Attribute name */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (!http || !dest || !dinfo || !option)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (NULL);
+  }
+
+ /*
+  * Find and return the attribute...
+  */
+
+  snprintf(name, sizeof(name), "%s-supported", option);
+  return (ippFindAttribute(dinfo->attrs, name, IPP_TAG_ZERO));
+}
+
+
 /*
  * 'cupsFreeDestInfo()' - Free destination information obtained using
  *                        @link cupsCopyDestInfo@.
@@ -429,29 +809,114 @@ cupsFreeDestInfo(cups_dinfo_t *dinfo)    /* I - Destination information */
 
   _cupsStrFree(dinfo->resource);
 
-  ippDelete(dinfo->attrs);
-
   cupsArrayDelete(dinfo->constraints);
+  cupsArrayDelete(dinfo->resolvers);
 
   cupsArrayDelete(dinfo->localizations);
 
   cupsArrayDelete(dinfo->media_db);
 
+  cupsArrayDelete(dinfo->cached_db);
+
+  ippDelete(dinfo->ready_attrs);
+  cupsArrayDelete(dinfo->ready_db);
+
+  ippDelete(dinfo->attrs);
+
   free(dinfo);
 }
 
 
+/*
+ * 'cupsGetDestMediaByIndex()' - Get a media name, dimension, and margins for a
+ *                               specific size.
+ *
+ * The @code flags@ parameter determines which set of media are indexed.  For
+ * example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will get the Nth
+ * borderless size supported by the printer.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+int                                    /* O - 1 on success, 0 on failure */
+cupsGetDestMediaByIndex(
+    http_t       *http,                        /* I - Connection to destination */
+    cups_dest_t  *dest,                        /* I - Destination */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    int          n,                    /* I - Media size number (0-based) */
+    unsigned     flags,                        /* I - Media flags */
+    cups_size_t  *size)                        /* O - Media size information */
+{
+  _cups_media_db_t     *nsize;         /* Size for N */
+  pwg_media_t          *pwg;           /* PWG media name for size */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (size)
+    memset(size, 0, sizeof(cups_size_t));
+
+  if (!http || !dest || !dinfo || n < 0 || !size)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (0);
+  }
+
+ /*
+  * Load media list as needed...
+  */
+
+  if (flags & CUPS_MEDIA_FLAGS_READY)
+    cups_update_ready(http, dinfo);
+
+  if (!dinfo->cached_db || dinfo->cached_flags != flags)
+    cups_create_cached(http, dinfo, flags);
+
+ /*
+  * Copy the size over and return...
+  */
+
+  if ((nsize = (_cups_media_db_t *)cupsArrayIndex(dinfo->cached_db, n)) == NULL)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (0);
+  }
+
+  if (nsize->size_name)
+    strlcpy(size->media, nsize->size_name, sizeof(size->media));
+  else if (nsize->key)
+    strlcpy(size->media, nsize->key, sizeof(size->media));
+  else if ((pwg = pwgMediaForSize(nsize->width, nsize->length)) != NULL)
+    strlcpy(size->media, pwg->pwg, sizeof(size->media));
+  else
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (0);
+  }
+
+  size->width  = nsize->width;
+  size->length = nsize->length;
+  size->bottom = nsize->bottom;
+  size->left   = nsize->left;
+  size->right  = nsize->right;
+  size->top    = nsize->top;
+
+  return (1);
+}
+
+
 /*
  * 'cupsGetDestMediaByName()' - Get media names, dimensions, and margins.
  *
- * The "media" string is a PWG media name, while "width" and "length" are the
- * dimensions in hundredths of millimeters.  "flags" provides some matching
+ * The "media" string is a PWG media name.  "Flags" provides some matching
  * guidance (multiple flags can be combined):
  *
- * CUPS_MEDIA_FLAGS_DEFAULT    = find the closest size supported by the printer
- * CUPS_MEDIA_FLAGS_BORDERLESS = find a borderless size
- * CUPS_MEDIA_FLAGS_DUPLEX     = find a size compatible with 2-sided printing
- * CUPS_MEDIA_FLAGS_EXACT      = find an exact match for the size
+ * CUPS_MEDIA_FLAGS_DEFAULT    = find the closest size supported by the printer,
+ * CUPS_MEDIA_FLAGS_BORDERLESS = find a borderless size,
+ * CUPS_MEDIA_FLAGS_DUPLEX     = find a size compatible with 2-sided printing,
+ * CUPS_MEDIA_FLAGS_EXACT      = find an exact match for the size, and
  * CUPS_MEDIA_FLAGS_READY      = if the printer supports media sensing, find the
  *                               size amongst the "ready" media.
  *
@@ -459,7 +924,7 @@ cupsFreeDestInfo(cups_dinfo_t *dinfo)       /* I - Destination information */
  *
  * Returns 1 when there is a match and 0 if there is not a match.
  *
- * @since CUPS 1.6@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 int                                    /* O - 1 on match, 0 on failure */
@@ -471,7 +936,7 @@ cupsGetDestMediaByName(
     unsigned     flags,                        /* I - Media matching flags */
     cups_size_t  *size)                        /* O - Media size information */
 {
-  _pwg_media_t         *pwg;           /* PWG media info */
+  pwg_media_t          *pwg;           /* PWG media info */
 
 
  /*
@@ -483,7 +948,7 @@ cupsGetDestMediaByName(
 
   if (!http || !dest || !dinfo || !media || !size)
   {
-    _cupsSetError(IPP_INTERNAL_ERROR, strerror(EINVAL), 0);
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
     return (0);
   }
 
@@ -491,11 +956,11 @@ cupsGetDestMediaByName(
   * Lookup the media size name...
   */
 
-  if ((pwg = _pwgMediaForPWG(media)) == NULL)
-    if ((pwg = _pwgMediaForLegacy(media)) == NULL)
+  if ((pwg = pwgMediaForPWG(media)) == NULL)
+    if ((pwg = pwgMediaForLegacy(media)) == NULL)
     {
       DEBUG_printf(("1cupsGetDestMediaByName: Unknown size '%s'.", media));
-      _cupsSetError(IPP_INTERNAL_ERROR, _("Unknown media size name."), 1);
+      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown media size name."), 1);
       return (0);
     }
 
@@ -503,44 +968,139 @@ cupsGetDestMediaByName(
   * Lookup the size...
   */
 
-  return (cups_get_media_db(dinfo, pwg, flags, size));
+  return (cups_get_media_db(http, dinfo, pwg, flags, size));
 }
 
 
 /*
  * 'cupsGetDestMediaBySize()' - Get media names, dimensions, and margins.
  *
- * The "media" string is a PWG media name, while "width" and "length" are the
- * dimensions in hundredths of millimeters.  "flags" provides some matching
- * guidance (multiple flags can be combined):
+ * "Width" and "length" are the dimensions in hundredths of millimeters.
+ * "Flags" provides some matching guidance (multiple flags can be combined):
  *
- * CUPS_MEDIA_FLAGS_DEFAULT    = find the closest size supported by the printer
- * CUPS_MEDIA_FLAGS_BORDERLESS = find a borderless size
- * CUPS_MEDIA_FLAGS_DUPLEX     = find a size compatible with 2-sided printing
- * CUPS_MEDIA_FLAGS_EXACT      = find an exact match for the size
+ * CUPS_MEDIA_FLAGS_DEFAULT    = find the closest size supported by the printer,
+ * CUPS_MEDIA_FLAGS_BORDERLESS = find a borderless size,
+ * CUPS_MEDIA_FLAGS_DUPLEX     = find a size compatible with 2-sided printing,
+ * CUPS_MEDIA_FLAGS_EXACT      = find an exact match for the size, and
  * CUPS_MEDIA_FLAGS_READY      = if the printer supports media sensing, find the
  *                               size amongst the "ready" media.
  *
  * The matching result (if any) is returned in the "cups_size_t" structure.
  *
- * Returns 1 when there is a match and 0 if there is not a match.
+ * Returns 1 when there is a match and 0 if there is not a match.
+ *
+ * @since CUPS 1.6/macOS 10.8@
+ */
+
+int                                    /* O - 1 on match, 0 on failure */
+cupsGetDestMediaBySize(
+    http_t       *http,                        /* I - Connection to destination */
+    cups_dest_t  *dest,                        /* I - Destination */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    int         width,                 /* I - Media width in hundredths of
+                                        *     of millimeters */
+    int         length,                        /* I - Media length in hundredths of
+                                        *     of millimeters */
+    unsigned     flags,                        /* I - Media matching flags */
+    cups_size_t  *size)                        /* O - Media size information */
+{
+  pwg_media_t          *pwg;           /* PWG media info */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (size)
+    memset(size, 0, sizeof(cups_size_t));
+
+  if (!http || !dest || !dinfo || width <= 0 || length <= 0 || !size)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (0);
+  }
+
+ /*
+  * Lookup the media size name...
+  */
+
+  if ((pwg = pwgMediaForSize(width, length)) == NULL)
+  {
+    DEBUG_printf(("1cupsGetDestMediaBySize: Invalid size %dx%d.", width,
+                  length));
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Invalid media size."), 1);
+    return (0);
+  }
+
+ /*
+  * Lookup the size...
+  */
+
+  return (cups_get_media_db(http, dinfo, pwg, flags, size));
+}
+
+
+/*
+ * 'cupsGetDestMediaCount()' - Get the number of sizes supported by a
+ *                             destination.
+ *
+ * The @code flags@ parameter determines the set of media sizes that are
+ * counted.  For example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will return
+ * the number of borderless sizes.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+int                                    /* O - Number of sizes */
+cupsGetDestMediaCount(
+    http_t       *http,                        /* I - Connection to destination */
+    cups_dest_t  *dest,                        /* I - Destination */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    unsigned     flags)                        /* I - Media flags */
+{
+ /*
+  * Range check input...
+  */
+
+  if (!http || !dest || !dinfo)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (0);
+  }
+
+ /*
+  * Load media list as needed...
+  */
+
+  if (flags & CUPS_MEDIA_FLAGS_READY)
+    cups_update_ready(http, dinfo);
+
+  if (!dinfo->cached_db || dinfo->cached_flags != flags)
+    cups_create_cached(http, dinfo, flags);
+
+  return (cupsArrayCount(dinfo->cached_db));
+}
+
+
+/*
+ * 'cupsGetDestMediaDefault()' - Get the default size for a destination.
+ *
+ * The @code flags@ parameter determines which default size is returned.  For
+ * example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will return the default
+ * borderless size, typically US Letter or A4, but sometimes 4x6 photo media.
  *
- * @since CUPS 1.6@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
-int                                    /* O - 1 on match, 0 on failure */
-cupsGetDestMediaBySize(
+int                                    /* O - 1 on success, 0 on failure */
+cupsGetDestMediaDefault(
     http_t       *http,                        /* I - Connection to destination */
     cups_dest_t  *dest,                        /* I - Destination */
     cups_dinfo_t *dinfo,               /* I - Destination information */
-    int         width,                 /* I - Media width in hundredths of
-                                        *     of millimeters */
-    int         length,                        /* I - Media length in hundredths of
-                                        *     of millimeters */
-    unsigned     flags,                        /* I - Media matching flags */
+    unsigned     flags,                        /* I - Media flags */
     cups_size_t  *size)                        /* O - Media size information */
 {
-  _pwg_media_t         *pwg;           /* PWG media info */
+  const char   *media;                 /* Default media size */
 
 
  /*
@@ -550,29 +1110,82 @@ cupsGetDestMediaBySize(
   if (size)
     memset(size, 0, sizeof(cups_size_t));
 
-  if (!http || !dest || !dinfo || width <= 0 || length <= 0 || !size)
+  if (!http || !dest || !dinfo || !size)
   {
-    _cupsSetError(IPP_INTERNAL_ERROR, strerror(EINVAL), 0);
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
     return (0);
   }
 
  /*
-  * Lookup the media size name...
+  * Get the default media size, if any...
   */
 
-  if ((pwg = _pwgMediaForSize(width, length)) == NULL)
-  {
-    DEBUG_printf(("1cupsGetDestMediaBySize: Invalid size %dx%d.", width,
-                  length));
-    _cupsSetError(IPP_INTERNAL_ERROR, _("Invalid media size."), 1);
-    return (0);
-  }
+  if ((media = cupsGetOption("media", dest->num_options,
+                             dest->options)) == NULL)
+    media = "na_letter_8.5x11in";
+
+  if (cupsGetDestMediaByName(http, dest, dinfo, media, flags, size))
+    return (1);
+
+  if (strcmp(media, "na_letter_8.5x11in") &&
+      cupsGetDestMediaByName(http, dest, dinfo, "iso_a4_210x297mm", flags,
+                             size))
+    return (1);
+
+  if (strcmp(media, "iso_a4_210x297mm") &&
+      cupsGetDestMediaByName(http, dest, dinfo, "na_letter_8.5x11in", flags,
+                             size))
+    return (1);
+
+  if ((flags & CUPS_MEDIA_FLAGS_BORDERLESS) &&
+      cupsGetDestMediaByName(http, dest, dinfo, "na_index_4x6in", flags, size))
+    return (1);
 
  /*
-  * Lookup the size...
+  * Fall back to the first matching media size...
   */
 
-  return (cups_get_media_db(dinfo, pwg, flags, size));
+  return (cupsGetDestMediaByIndex(http, dest, dinfo, 0, flags, size));
+}
+
+
+/*
+ * 'cups_add_dconstres()' - Add a constraint or resolver to an array.
+ */
+
+static void
+cups_add_dconstres(
+    cups_array_t *a,                   /* I - Array */
+    ipp_t        *collection)          /* I - Collection value */
+{
+  ipp_attribute_t      *attr;          /* Attribute */
+  _cups_dconstres_t    *temp;          /* Current constraint/resolver */
+
+
+  if ((attr = ippFindAttribute(collection, "resolver-name",
+                               IPP_TAG_NAME)) == NULL)
+    return;
+
+  if ((temp = calloc(1, sizeof(_cups_dconstres_t))) == NULL)
+    return;
+
+  temp->name       = attr->values[0].string.text;
+  temp->collection = collection;
+
+  cupsArrayAdd(a, temp);
+}
+
+
+/*
+ * 'cups_compare_dconstres()' - Compare to resolver entries.
+ */
+
+static int                             /* O - Result of comparison */
+cups_compare_dconstres(
+    _cups_dconstres_t *a,              /* I - First resolver */
+    _cups_dconstres_t *b)              /* I - Second resolver */
+{
+  return (strcmp(a->name, b->name));
 }
 
 
@@ -633,13 +1246,181 @@ cups_copy_media_db(
 }
 
 
+/*
+ * 'cups_create_cached()' - Create the media selection cache.
+ */
+
+static void
+cups_create_cached(http_t       *http, /* I - Connection to destination */
+                   cups_dinfo_t *dinfo,        /* I - Destination information */
+                   unsigned     flags) /* I - Media selection flags */
+{
+  cups_array_t         *db;            /* Media database array to use */
+  _cups_media_db_t     *mdb,           /* Media database entry */
+                       *first;         /* First entry this size */
+
+
+  DEBUG_printf(("3cups_create_cached(http=%p, dinfo=%p, flags=%u)", (void *)http, (void *)dinfo, flags));
+
+  if (dinfo->cached_db)
+    cupsArrayDelete(dinfo->cached_db);
+
+  dinfo->cached_db    = cupsArrayNew(NULL, NULL);
+  dinfo->cached_flags = flags;
+
+  if (flags & CUPS_MEDIA_FLAGS_READY)
+  {
+    DEBUG_puts("4cups_create_cached: ready media");
+
+    cups_update_ready(http, dinfo);
+    db = dinfo->ready_db;
+  }
+  else
+  {
+    DEBUG_puts("4cups_create_cached: supported media");
+
+    if (!dinfo->media_db)
+      cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_DEFAULT);
+
+    db = dinfo->media_db;
+  }
+
+  for (mdb = (_cups_media_db_t *)cupsArrayFirst(db), first = mdb;
+       mdb;
+       mdb = (_cups_media_db_t *)cupsArrayNext(db))
+  {
+    DEBUG_printf(("4cups_create_cached: %p key=\"%s\", type=\"%s\", %dx%d, B%d L%d R%d T%d", (void *)mdb, mdb->key, mdb->type, mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top));
+
+    if (flags & CUPS_MEDIA_FLAGS_BORDERLESS)
+    {
+      if (!mdb->left && !mdb->right && !mdb->top && !mdb->bottom)
+      {
+        DEBUG_printf(("4cups_create_cached: add %p", (void *)mdb));
+        cupsArrayAdd(dinfo->cached_db, mdb);
+      }
+    }
+    else if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
+    {
+      if (first->width != mdb->width || first->length != mdb->length)
+      {
+       DEBUG_printf(("4cups_create_cached: add %p", (void *)first));
+        cupsArrayAdd(dinfo->cached_db, first);
+        first = mdb;
+      }
+      else if (mdb->left >= first->left && mdb->right >= first->right && mdb->top >= first->top && mdb->bottom >= first->bottom &&
+              (mdb->left != first->left || mdb->right != first->right || mdb->top != first->top || mdb->bottom != first->bottom))
+        first = mdb;
+    }
+    else
+    {
+      DEBUG_printf(("4cups_create_cached: add %p", (void *)mdb));
+      cupsArrayAdd(dinfo->cached_db, mdb);
+    }
+  }
+
+  if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
+  {
+    DEBUG_printf(("4cups_create_cached: add %p", (void *)first));
+    cupsArrayAdd(dinfo->cached_db, first);
+  }
+}
+
+
+/*
+ * 'cups_create_constraints()' - Create the constraints and resolvers arrays.
+ */
+
+static void
+cups_create_constraints(
+    cups_dinfo_t *dinfo)               /* I - Destination information */
+{
+  int                  i;              /* Looping var */
+  ipp_attribute_t      *attr;          /* Attribute */
+  _ipp_value_t         *val;           /* Current value */
+
+
+  dinfo->constraints = cupsArrayNew3(NULL, NULL, NULL, 0, NULL,
+                                     (cups_afree_func_t)free);
+  dinfo->resolvers   = cupsArrayNew3((cups_array_func_t)cups_compare_dconstres,
+                                    NULL, NULL, 0, NULL,
+                                     (cups_afree_func_t)free);
+
+  if ((attr = ippFindAttribute(dinfo->attrs, "job-constraints-supported",
+                              IPP_TAG_BEGIN_COLLECTION)) != NULL)
+  {
+    for (i = attr->num_values, val = attr->values; i > 0; i --, val ++)
+      cups_add_dconstres(dinfo->constraints, val->collection);
+  }
+
+  if ((attr = ippFindAttribute(dinfo->attrs, "job-resolvers-supported",
+                              IPP_TAG_BEGIN_COLLECTION)) != NULL)
+  {
+    for (i = attr->num_values, val = attr->values; i > 0; i --, val ++)
+      cups_add_dconstres(dinfo->resolvers, val->collection);
+  }
+}
+
+
+/*
+ * 'cups_create_defaults()' - Create the -default option array.
+ *
+ * TODO: Need to support collection defaults...
+ */
+
+static void
+cups_create_defaults(
+    cups_dinfo_t *dinfo)               /* I - Destination information */
+{
+  ipp_attribute_t      *attr;          /* Current attribute */
+  char                 name[IPP_MAX_NAME + 1],
+                                       /* Current name */
+                       *nameptr,       /* Pointer into current name */
+                       value[2048];    /* Current value */
+
+
+ /*
+  * Iterate through the printer attributes looking for xxx-default and adding
+  * xxx=value to the defaults option array.
+  */
+
+  for (attr = ippFirstAttribute(dinfo->attrs);
+       attr;
+       attr = ippNextAttribute(dinfo->attrs))
+  {
+    if (!attr->name || attr->group_tag != IPP_TAG_PRINTER)
+      continue;
+
+    if (attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
+      continue;                                /* TODO: STR #4096 */
+
+    if ((nameptr = attr->name + strlen(attr->name) - 8) <= attr->name ||
+        strcmp(nameptr, "-default"))
+      continue;
+
+    strlcpy(name, attr->name, sizeof(name));
+    if ((nameptr = name + strlen(name) - 8) <= name ||
+        strcmp(nameptr, "-default"))
+      continue;
+
+    *nameptr = '\0';
+
+    if (ippAttributeString(attr, value, sizeof(value)) >= sizeof(value))
+      continue;
+
+    dinfo->num_defaults = cupsAddOption(name, value, dinfo->num_defaults,
+                                        &dinfo->defaults);
+  }
+}
+
+
 /*
  * 'cups_create_media_db()' - Create the media database.
  */
 
 static void
 cups_create_media_db(
-    cups_dinfo_t *dinfo)               /* I - Destination information */
+    cups_dinfo_t *dinfo,               /* I - Destination information */
+    unsigned     flags)                        /* I - Media flags */
 {
   int                  i;              /* Looping var */
   _ipp_value_t         *val;           /* Current value */
@@ -647,21 +1428,40 @@ cups_create_media_db(
                        *media_attr,    /* media-xxx */
                        *x_dimension,   /* x-dimension */
                        *y_dimension;   /* y-dimension */
-  _pwg_media_t         *pwg;           /* PWG media info */
+  pwg_media_t          *pwg;           /* PWG media info */
+  cups_array_t         *db;            /* New media database array */
   _cups_media_db_t     mdb;            /* Media entry */
 
 
-  dinfo->media_db = cupsArrayNew3((cups_array_func_t)cups_compare_media_db,
-                                  NULL, NULL, 0,
-                                  (cups_acopy_func_t)cups_copy_media_db,
-                                  (cups_afree_func_t)cups_free_media_db);
-  dinfo->min_size.width  = INT_MAX;
-  dinfo->min_size.length = INT_MAX;
-  dinfo->max_size.width  = 0;
-  dinfo->max_size.length = 0;
+  db = cupsArrayNew3((cups_array_func_t)cups_compare_media_db,
+                    NULL, NULL, 0,
+                    (cups_acopy_func_t)cups_copy_media_db,
+                    (cups_afree_func_t)cups_free_media_db);
+
+  if (flags == CUPS_MEDIA_FLAGS_READY)
+  {
+    dinfo->ready_db = db;
+
+    media_col_db = ippFindAttribute(dinfo->ready_attrs, "media-col-ready",
+                                   IPP_TAG_BEGIN_COLLECTION);
+    media_attr   = ippFindAttribute(dinfo->ready_attrs, "media-ready",
+                                   IPP_TAG_ZERO);
+  }
+  else
+  {
+    dinfo->media_db        = db;
+    dinfo->min_size.width  = INT_MAX;
+    dinfo->min_size.length = INT_MAX;
+    dinfo->max_size.width  = 0;
+    dinfo->max_size.length = 0;
+
+    media_col_db = ippFindAttribute(dinfo->attrs, "media-col-database",
+                                   IPP_TAG_BEGIN_COLLECTION);
+    media_attr   = ippFindAttribute(dinfo->attrs, "media-supported",
+                                   IPP_TAG_ZERO);
+  }
 
-  if ((media_col_db = ippFindAttribute(dinfo->attrs, "media-col-database",
-                                       IPP_TAG_BEGIN_COLLECTION)) != NULL)
+  if (media_col_db)
   {
     _ipp_value_t       *custom = NULL; /* Custom size range value */
 
@@ -678,15 +1478,32 @@ cups_create_media_db(
                                        /* media-size collection value */
 
         if ((x_dimension = ippFindAttribute(media_size, "x-dimension",
-                                          IPP_TAG_INTEGER)) != NULL &&
+                                            IPP_TAG_INTEGER)) != NULL &&
            (y_dimension = ippFindAttribute(media_size, "y-dimension",
                                            IPP_TAG_INTEGER)) != NULL)
        {
+        /*
+         * Fixed size...
+         */
+
          mdb.width  = x_dimension->values[0].integer;
          mdb.length = y_dimension->values[0].integer;
        }
-        else if ((x_dimension = ippFindAttribute(media_size, "x-dimension",
-                                              IPP_TAG_RANGE)) != NULL &&
+       else if ((x_dimension = ippFindAttribute(media_size, "x-dimension",
+                                                IPP_TAG_INTEGER)) != NULL &&
+                (y_dimension = ippFindAttribute(media_size, "y-dimension",
+                                                IPP_TAG_RANGE)) != NULL)
+       {
+        /*
+         * Roll limits...
+         */
+
+         mdb.width  = x_dimension->values[0].integer;
+         mdb.length = y_dimension->values[0].range.upper;
+       }
+        else if (flags != CUPS_MEDIA_FLAGS_READY &&
+                 (x_dimension = ippFindAttribute(media_size, "x-dimension",
+                                                IPP_TAG_RANGE)) != NULL &&
                 (y_dimension = ippFindAttribute(media_size, "y-dimension",
                                                 IPP_TAG_RANGE)) != NULL)
        {
@@ -710,7 +1527,6 @@ cups_create_media_db(
          dinfo->max_size.right  = 635; /* Default 1/4" side margins */
          dinfo->max_size.top    =
          dinfo->max_size.bottom = 1270; /* Default 1/2" top/bottom margins */
-
          continue;
        }
       }
@@ -770,7 +1586,7 @@ cups_create_media_db(
                                          IPP_TAG_INTEGER)) != NULL)
         mdb.top = media_attr->values[0].integer;
 
-      cupsArrayAdd(dinfo->media_db, &mdb);
+      cupsArrayAdd(db, &mdb);
     }
 
     if (custom)
@@ -808,8 +1624,7 @@ cups_create_media_db(
       }
     }
   }
-  else if ((media_attr = ippFindAttribute(dinfo->attrs, "media-supported",
-                                          IPP_TAG_ZERO)) != NULL &&
+  else if (media_attr &&
            (media_attr->value_tag == IPP_TAG_NAME ||
             media_attr->value_tag == IPP_TAG_NAMELANG ||
             media_attr->value_tag == IPP_TAG_KEYWORD))
@@ -825,8 +1640,8 @@ cups_create_media_db(
          i > 0;
          i --, val ++)
     {
-      if ((pwg = _pwgMediaForPWG(val->string.text)) == NULL)
-        if ((pwg = _pwgMediaForLegacy(val->string.text)) == NULL)
+      if ((pwg = pwgMediaForPWG(val->string.text)) == NULL)
+        if ((pwg = pwgMediaForLegacy(val->string.text)) == NULL)
        {
          DEBUG_printf(("3cups_create_media_db: Ignoring unknown size '%s'.",
                        val->string.text));
@@ -836,12 +1651,14 @@ cups_create_media_db(
       mdb.width  = pwg->width;
       mdb.length = pwg->length;
 
-      if (!strncmp(val->string.text, "custom_min_", 11))
+      if (flags != CUPS_MEDIA_FLAGS_READY &&
+          !strncmp(val->string.text, "custom_min_", 11))
       {
         mdb.size_name   = NULL;
         dinfo->min_size = mdb;
       }
-      else if (!strncmp(val->string.text, "custom_max_", 11))
+      else if (flags != CUPS_MEDIA_FLAGS_READY &&
+              !strncmp(val->string.text, "custom_max_", 11))
       {
         mdb.size_name   = NULL;
         dinfo->max_size = mdb;
@@ -850,7 +1667,7 @@ cups_create_media_db(
       {
         mdb.size_name = val->string.text;
 
-        cupsArrayAdd(dinfo->media_db, &mdb);
+        cupsArrayAdd(db, &mdb);
       }
     }
   }
@@ -887,11 +1704,13 @@ cups_free_media_db(
  */
 
 static int                             /* O - 1 on match, 0 on failure */
-cups_get_media_db(cups_dinfo_t *dinfo, /* I - Destination information */
-                  _pwg_media_t *pwg,   /* I - PWG media info */
+cups_get_media_db(http_t       *http,  /* I - Connection to destination */
+                  cups_dinfo_t *dinfo, /* I - Destination information */
+                  pwg_media_t  *pwg,   /* I - PWG media info */
                   unsigned     flags,  /* I - Media matching flags */
-                  cups_size_t *size)   /* O - Media size/margin/name info */
+                  cups_size_t  *size)  /* O - Media size/margin/name info */
 {
+  cups_array_t         *db;            /* Which media database to query */
   _cups_media_db_t     *mdb,           /* Current media database entry */
                        *best = NULL,   /* Best matching entry */
                        key;            /* Search key */
@@ -901,8 +1720,18 @@ cups_get_media_db(cups_dinfo_t *dinfo,    /* I - Destination information */
   * Create the media database as needed...
   */
 
-  if (!dinfo->media_db)
-    cups_create_media_db(dinfo);
+  if (flags & CUPS_MEDIA_FLAGS_READY)
+  {
+    cups_update_ready(http, dinfo);
+    db = dinfo->ready_db;
+  }
+  else
+  {
+    if (!dinfo->media_db)
+      cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_DEFAULT);
+
+    db = dinfo->media_db;
+  }
 
  /*
   * Find a match...
@@ -912,7 +1741,7 @@ cups_get_media_db(cups_dinfo_t *dinfo,     /* I - Destination information */
   key.width  = pwg->width;
   key.length = pwg->length;
 
-  if ((mdb = cupsArrayFind(dinfo->media_db, &key)) != NULL)
+  if ((mdb = cupsArrayFind(db, &key)) != NULL)
   {
    /*
     * Found an exact match, let's figure out the best margins for the flags
@@ -927,12 +1756,11 @@ cups_get_media_db(cups_dinfo_t *dinfo,   /* I - Destination information */
       * Look for the smallest margins...
       */
 
-      if (best->left != 0 || best->right != 0 || best->top != 0 ||
-          best->bottom != 0)
+      if (best->left != 0 || best->right != 0 || best->top != 0 || best->bottom != 0)
       {
-       for (mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db);
+       for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
             mdb && !cups_compare_media_db(mdb, &key);
-            mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+            mdb = (_cups_media_db_t *)cupsArrayNext(db))
        {
          if (mdb->left <= best->left && mdb->right <= best->right &&
              mdb->top <= best->top && mdb->bottom <= best->bottom)
@@ -960,12 +1788,13 @@ cups_get_media_db(cups_dinfo_t *dinfo,   /* I - Destination information */
       * Look for the largest margins...
       */
 
-      for (mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db);
+      for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
           mdb && !cups_compare_media_db(mdb, &key);
-          mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+          mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (mdb->left >= best->left && mdb->right >= best->right &&
-           mdb->top >= best->top && mdb->bottom >= best->bottom)
+           mdb->top >= best->top && mdb->bottom >= best->bottom &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -975,16 +1804,15 @@ cups_get_media_db(cups_dinfo_t *dinfo,   /* I - Destination information */
       * Look for the smallest non-zero margins...
       */
 
-      for (mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db);
+      for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
           mdb && !cups_compare_media_db(mdb, &key);
-          mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+          mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (((mdb->left > 0 && mdb->left <= best->left) || best->left == 0) &&
-           ((mdb->right > 0 && mdb->right <= best->right) ||
-            best->right == 0) &&
+           ((mdb->right > 0 && mdb->right <= best->right) || best->right == 0) &&
            ((mdb->top > 0 && mdb->top <= best->top) || best->top == 0) &&
-           ((mdb->bottom > 0 && mdb->bottom <= best->bottom) ||
-            best->bottom == 0))
+           ((mdb->bottom > 0 && mdb->bottom <= best->bottom) || best->bottom == 0) &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1037,9 +1865,9 @@ cups_get_media_db(cups_dinfo_t *dinfo,    /* I - Destination information */
     * Find a close size...
     */
 
-    for (mdb = (_cups_media_db_t *)cupsArrayFirst(dinfo->media_db);
+    for (mdb = (_cups_media_db_t *)cupsArrayFirst(db);
          mdb;
-         mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+         mdb = (_cups_media_db_t *)cupsArrayNext(db))
       if (cups_is_close_media_db(mdb, &key))
         break;
 
@@ -1057,12 +1885,13 @@ cups_get_media_db(cups_dinfo_t *dinfo,  /* I - Destination information */
       if (best->left != 0 || best->right != 0 || best->top != 0 ||
           best->bottom != 0)
       {
-       for (mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db);
+       for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
             mdb && cups_is_close_media_db(mdb, &key);
-            mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+            mdb = (_cups_media_db_t *)cupsArrayNext(db))
        {
          if (mdb->left <= best->left && mdb->right <= best->right &&
-             mdb->top <= best->top && mdb->bottom <= best->bottom)
+             mdb->top <= best->top && mdb->bottom <= best->bottom &&
+             (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          {
            best = mdb;
            if (mdb->left == 0 && mdb->right == 0 && mdb->bottom == 0 &&
@@ -1078,12 +1907,13 @@ cups_get_media_db(cups_dinfo_t *dinfo,  /* I - Destination information */
       * Look for the largest margins...
       */
 
-      for (mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db);
+      for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
           mdb && cups_is_close_media_db(mdb, &key);
-          mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+          mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (mdb->left >= best->left && mdb->right >= best->right &&
-           mdb->top >= best->top && mdb->bottom >= best->bottom)
+           mdb->top >= best->top && mdb->bottom >= best->bottom &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1093,16 +1923,17 @@ cups_get_media_db(cups_dinfo_t *dinfo,  /* I - Destination information */
       * Look for the smallest non-zero margins...
       */
 
-      for (mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db);
+      for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
           mdb && cups_is_close_media_db(mdb, &key);
-          mdb = (_cups_media_db_t *)cupsArrayNext(dinfo->media_db))
+          mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (((mdb->left > 0 && mdb->left <= best->left) || best->left == 0) &&
            ((mdb->right > 0 && mdb->right <= best->right) ||
             best->right == 0) &&
            ((mdb->top > 0 && mdb->top <= best->top) || best->top == 0) &&
            ((mdb->bottom > 0 && mdb->bottom <= best->bottom) ||
-            best->bottom == 0))
+            best->bottom == 0) &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1160,5 +1991,275 @@ cups_is_close_media_db(
 
 
 /*
- * End of "$Id$".
+ * 'cups_test_constraints()' - Test constraints.
+ *
+ * TODO: STR #4096 - Need to properly support media-col contraints...
+ */
+
+static cups_array_t *                  /* O - Active constraints */
+cups_test_constraints(
+    cups_dinfo_t  *dinfo,              /* I - Destination information */
+    const char    *new_option,         /* I - Newly selected option */
+    const char    *new_value,          /* I - Newly selected value */
+    int           num_options,         /* I - Number of options */
+    cups_option_t *options,            /* I - Options */
+    int           *num_conflicts,      /* O - Number of conflicting options */
+    cups_option_t **conflicts)         /* O - Conflicting options */
+{
+  int                  i,              /* Looping var */
+                       match;          /* Value matches? */
+  int                  num_matching;   /* Number of matching options */
+  cups_option_t                *matching;      /* Matching options */
+  _cups_dconstres_t    *c;             /* Current constraint */
+  cups_array_t         *active = NULL; /* Active constraints */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  _ipp_value_t         *attrval;       /* Current attribute value */
+  const char           *value;         /* Current value */
+  char                 temp[1024];     /* Temporary string */
+  int                  int_value;      /* Integer value */
+  int                  xres_value,     /* Horizontal resolution */
+                       yres_value;     /* Vertical resolution */
+  ipp_res_t            units_value;    /* Resolution units */
+
+
+  for (c = (_cups_dconstres_t *)cupsArrayFirst(dinfo->constraints);
+       c;
+       c = (_cups_dconstres_t *)cupsArrayNext(dinfo->constraints))
+  {
+    num_matching = 0;
+    matching     = NULL;
+
+    for (attr = ippFirstAttribute(c->collection);
+         attr;
+         attr = ippNextAttribute(c->collection))
+    {
+      if (attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
+        break;                         /* TODO: STR #4096 */
+
+     /*
+      * Get the value for the current attribute in the constraint...
+      */
+
+      if (new_option && new_value && !strcmp(attr->name, new_option))
+        value = new_value;
+      else if ((value = cupsGetOption(attr->name, num_options,
+                                      options)) == NULL)
+        value = cupsGetOption(attr->name, dinfo->num_defaults, dinfo->defaults);
+
+      if (!value)
+      {
+       /*
+        * Not set so this constraint does not apply...
+        */
+
+        break;
+      }
+
+      match = 0;
+
+      switch (attr->value_tag)
+      {
+        case IPP_TAG_INTEGER :
+        case IPP_TAG_ENUM :
+           int_value = atoi(value);
+
+           for (i = attr->num_values, attrval = attr->values;
+                i > 0;
+                i --, attrval ++)
+           {
+             if (attrval->integer == int_value)
+             {
+               match = 1;
+               break;
+             }
+            }
+            break;
+
+        case IPP_TAG_BOOLEAN :
+           int_value = !strcmp(value, "true");
+
+           for (i = attr->num_values, attrval = attr->values;
+                i > 0;
+                i --, attrval ++)
+           {
+             if (attrval->boolean == int_value)
+             {
+               match = 1;
+               break;
+             }
+            }
+            break;
+
+        case IPP_TAG_RANGE :
+           int_value = atoi(value);
+
+           for (i = attr->num_values, attrval = attr->values;
+                i > 0;
+                i --, attrval ++)
+           {
+             if (int_value >= attrval->range.lower &&
+                 int_value <= attrval->range.upper)
+             {
+               match = 1;
+               break;
+             }
+            }
+            break;
+
+        case IPP_TAG_RESOLUTION :
+           if (sscanf(value, "%dx%d%15s", &xres_value, &yres_value, temp) != 3)
+           {
+             if (sscanf(value, "%d%15s", &xres_value, temp) != 2)
+               break;
+
+             yres_value = xres_value;
+           }
+
+           if (!strcmp(temp, "dpi"))
+             units_value = IPP_RES_PER_INCH;
+           else if (!strcmp(temp, "dpc") || !strcmp(temp, "dpcm"))
+             units_value = IPP_RES_PER_CM;
+           else
+             break;
+
+           for (i = attr->num_values, attrval = attr->values;
+                i > 0;
+                i --, attrval ++)
+           {
+             if (attrval->resolution.xres == xres_value &&
+                 attrval->resolution.yres == yres_value &&
+                 attrval->resolution.units == units_value)
+             {
+               match = 1;
+               break;
+             }
+           }
+            break;
+
+       case IPP_TAG_TEXT :
+       case IPP_TAG_NAME :
+       case IPP_TAG_KEYWORD :
+       case IPP_TAG_CHARSET :
+       case IPP_TAG_URI :
+       case IPP_TAG_URISCHEME :
+       case IPP_TAG_MIMETYPE :
+       case IPP_TAG_LANGUAGE :
+       case IPP_TAG_TEXTLANG :
+       case IPP_TAG_NAMELANG :
+           for (i = attr->num_values, attrval = attr->values;
+                i > 0;
+                i --, attrval ++)
+           {
+             if (!strcmp(attrval->string.text, value))
+             {
+               match = 1;
+               break;
+             }
+            }
+           break;
+
+        default :
+            break;
+      }
+
+      if (!match)
+        break;
+
+      num_matching = cupsAddOption(attr->name, value, num_matching, &matching);
+    }
+
+    if (!attr)
+    {
+      if (!active)
+        active = cupsArrayNew(NULL, NULL);
+
+      cupsArrayAdd(active, c);
+
+      if (num_conflicts && conflicts)
+      {
+        cups_option_t  *moption;       /* Matching option */
+
+        for (i = num_matching, moption = matching; i > 0; i --, moption ++)
+          *num_conflicts = cupsAddOption(moption->name, moption->value,
+                                        *num_conflicts, conflicts);
+      }
+    }
+
+    cupsFreeOptions(num_matching, matching);
+  }
+
+  return (active);
+}
+
+
+/*
+ * 'cups_update_ready()' - Update xxx-ready attributes for the printer.
  */
+
+static void
+cups_update_ready(http_t       *http,  /* I - Connection to destination */
+                  cups_dinfo_t *dinfo) /* I - Destination information */
+{
+  ipp_t        *request;                       /* Get-Printer-Attributes request */
+  static const char * const pattrs[] = /* Printer attributes we want */
+  {
+    "finishings-col-ready",
+    "finishings-ready",
+    "job-finishings-col-ready",
+    "job-finishings-ready",
+    "media-col-ready",
+    "media-ready"
+  };
+
+
+ /*
+  * Don't update more than once every 30 seconds...
+  */
+
+  if ((time(NULL) - dinfo->ready_time) < _CUPS_MEDIA_READY_TTL)
+    return;
+
+ /*
+  * Free any previous results...
+  */
+
+  if (dinfo->cached_flags & CUPS_MEDIA_FLAGS_READY)
+  {
+    cupsArrayDelete(dinfo->cached_db);
+    dinfo->cached_db    = NULL;
+    dinfo->cached_flags = CUPS_MEDIA_FLAGS_DEFAULT;
+  }
+
+  ippDelete(dinfo->ready_attrs);
+  dinfo->ready_attrs = NULL;
+
+  cupsArrayDelete(dinfo->ready_db);
+  dinfo->ready_db = NULL;
+
+ /*
+  * Query the xxx-ready values...
+  */
+
+  request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+  ippSetVersion(request, dinfo->version / 10, dinfo->version % 10);
+
+  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
+               dinfo->uri);
+  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+               NULL, cupsUser());
+  ippAddStrings(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_KEYWORD), "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
+
+  dinfo->ready_attrs = cupsDoRequest(http, request, dinfo->resource);
+
+ /*
+  * Update the ready media database...
+  */
+
+  cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_READY);
+
+ /*
+  * Update last lookup time and return...
+  */
+
+  dinfo->ready_time = time(NULL);
+}