Back to index

system-config-printer  1.3.9+20120706
xmldriverprefs.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## system-config-printer
00004 
00005 ## Copyright (C) 2006, 2007, 2008, 2009, 2010 Red Hat, Inc.
00006 ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
00007 ## Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
00008 
00009 ## This program is free software; you can redistribute it and/or modify
00010 ## it under the terms of the GNU General Public License as published by
00011 ## the Free Software Foundation; either version 2 of the License, or
00012 ## (at your option) any later version.
00013 
00014 ## This program is distributed in the hope that it will be useful,
00015 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 ## GNU General Public License for more details.
00018 
00019 ## You should have received a copy of the GNU General Public License
00020 ## along with this program; if not, write to the Free Software
00021 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00022 
00023 import fnmatch
00024 import re
00025 import xml.etree.ElementTree
00026 from .cupshelpers import parseDeviceID
00027 import ppds
00028 
00029 def PreferredDrivers (filename):
00030     preferreddrivers = xml.etree.ElementTree.XML (file (filename).read ())
00031     return preferreddrivers.getchildren()
00032 
00033 class DeviceIDMatch:
00034     """
00035     A set of fields and regular expressions for matching a Device ID.
00036     """
00037     def __init__ (self):
00038         self._re = dict()
00039 
00040     def add_field (self, field, pattern):
00041         self._re[field.upper ()] = re.compile (pattern, re.I)
00042 
00043     def match (self, deviceid):
00044         """
00045         Match against a parsed Device ID dictionary.
00046 
00047         The CMD field is treated specially.  If any of the
00048         comma-separated words in this field value match, the Device ID
00049         pattern is considered to match.
00050         """
00051 
00052         for field, match in self._re.iteritems ():
00053             if not deviceid.has_key (field):
00054                 return False
00055 
00056             if field == "CMD":
00057                 this_field_matches = False
00058                 for cmd in deviceid[field]:
00059                     if match.match (cmd):
00060                         this_field_matches = True
00061                         break
00062 
00063                 if not this_field_matches:
00064                     return False
00065 
00066             if not match.match (deviceid[field]):
00067                 return False
00068 
00069         return True
00070 
00071 class DriverType:
00072     """
00073     A type of driver.
00074     """
00075 
00076     FIT_EXACT_CMD =     "exact-cmd"
00077     FIT_EXACT =         "exact"
00078     FIT_CLOSE =         "close"
00079     FIT_GENERIC =       "generic"
00080     FIT_NONE =          "none"
00081 
00082     def __init__ (self, name):
00083         self.name = name
00084         self.ppd_name = None
00085         self.attributes = []
00086         self.deviceid = []
00087 
00088         class AlwaysTrue:
00089             def get (self, k, d=None):
00090                 return True
00091 
00092         self._fit = AlwaysTrue ()
00093         self._packagehint = None
00094 
00095     def add_ppd_name (self, pattern):
00096         """
00097         An optional PPD name regular expression.
00098         """
00099         self.ppd_name = re.compile (pattern, re.I)
00100 
00101         # If the PPD name pattern includes a scheme, we can perhaps
00102         # deduce which package would provide this driver type.
00103         if self._packagehint != None:
00104             return
00105 
00106         parts = pattern.split (":", 1)
00107         if len (parts) > 1:
00108             scheme = parts[0]
00109             if scheme == "drv":
00110                 rest = parts[1]
00111                 if rest.startswith ("///"):
00112                     drv = rest[3:]
00113                     f = drv.rfind ("/")
00114                     if f != -1:
00115                         drv = drv[:f]
00116                         self._packagehint = "/usr/share/cups/drv/%s" % drv
00117             else:
00118                 self._packagehint = "/usr/lib/cups/driver/%s" % scheme
00119 
00120     def add_attribute (self, name, pattern):
00121         """
00122         An optional IPP attribute name and regular expression to match
00123         against its values.
00124         """
00125         self.attributes.append ((name, re.compile (pattern, re.I)))
00126 
00127     def add_deviceid_match (self, deviceid_match):
00128         """
00129         An optional IEEE 1284 Device ID match.
00130         """
00131         self.deviceid.append (deviceid_match)
00132 
00133     def add_fit (self, text):
00134         self._fit = {}
00135         for fittype in text.split():
00136             self._fit[fittype] = True
00137 
00138             # <fit>exact</fit> matches exact-cmd as well
00139             if fittype == self.FIT_EXACT:
00140                 self._fit[self.FIT_EXACT_CMD] = True
00141 
00142     def set_packagehint (self, hint):
00143         self._packagekit = hint
00144 
00145     def get_name (self):
00146         """
00147         Return the name for this driver type.
00148         """
00149         return self.name
00150 
00151     def __repr__ (self):
00152         return "<DriverType %s instance at 0x%x>" % (self.name, id (self))
00153 
00154     def match (self, ppd_name, attributes, fit):
00155         """
00156         Return True if there is a match for all specified criteria.
00157 
00158         ppdname: string
00159 
00160         attributes: dict
00161 
00162         fit: string
00163         """
00164 
00165         matches = self._fit.get (fit, False)
00166         if matches and self.ppd_name and not self.ppd_name.match (ppd_name):
00167             matches = False
00168 
00169         if matches:
00170             for name, match in self.attributes:
00171                 if not attributes.has_key (name):
00172                     matches = False
00173                     break
00174 
00175                 values = attributes[name]
00176                 if not isinstance (values, list):
00177                     # In case getPPDs() was used instead of getPPDs2()
00178                     values = [values]
00179 
00180                 any_value_matches = False
00181                 for value in values:
00182                     if match.match (value):
00183                         any_value_matches = True
00184                         break
00185 
00186                 if not any_value_matches:
00187                     matches = False
00188                     break
00189 
00190         if matches:
00191             if self.deviceid and not attributes.has_key ("ppd-device-id"):
00192                 matches = False
00193             elif self.deviceid:
00194                 # This is a match if any of the ppd-device-id values
00195                 # match.
00196                 deviceidlist = attributes["ppd-device-id"]
00197                 if not isinstance (deviceidlist, list):
00198                     # In case getPPDs() was used instead of getPPDs2()
00199                     deviceidlist = [deviceidlist]
00200 
00201                 any_id_matches = False
00202                 for deviceidstr in deviceidlist:
00203                     deviceid = parseDeviceID (deviceidstr)
00204                     for match in self.deviceid:
00205                         if match.match (deviceid):
00206                             any_id_matches = True
00207                             break
00208 
00209                 if not any_id_matches:
00210                     matches = False
00211 
00212         return matches
00213 
00214     def get_packagehint (self):
00215         return None
00216 
00217 class DriverTypes:
00218     """
00219     A list of driver types.
00220     """
00221 
00222     def __init__ (self):
00223         self.drivertypes = []
00224 
00225     def load (self, drivertypes):
00226         """
00227         Load the list of driver types from an XML file.
00228         """
00229 
00230         types = []
00231         for drivertype in drivertypes.getchildren ():
00232             t = DriverType (drivertype.attrib["name"])
00233 
00234             for child in drivertype.getchildren ():
00235                 if child.tag == "ppdname":
00236                     t.add_ppd_name (child.attrib["match"])
00237                 elif child.tag == "attribute":
00238                     t.add_attribute (child.attrib["name"],
00239                                      child.attrib["match"])
00240                 elif child.tag == "deviceid":
00241                     deviceid_match = DeviceIDMatch ()
00242                     for field in child.getchildren ():
00243                         if field.tag == "field":
00244                             deviceid_match.add_field (field.attrib["name"],
00245                                                       field.attrib["match"])
00246 
00247                     t.add_deviceid_match (deviceid_match)
00248                 elif child.tag == "fit":
00249                     t.add_fit (child.text)
00250 
00251             types.append (t)
00252 
00253         self.drivertypes = types
00254 
00255     def match (self, ppdname, ppddict, fit):
00256         """
00257         Return the first matching drivertype for a PPD, given its name,
00258         attributes, and fitness, or None if there is no match.
00259         """
00260 
00261         for drivertype in self.drivertypes:
00262             if drivertype.match (ppdname, ppddict, fit):
00263                 return drivertype
00264 
00265         return None
00266 
00267     def filter (self, pattern):
00268         """
00269         Return the subset of driver type names that match a glob
00270         pattern.
00271         """
00272 
00273         return fnmatch.filter (map (lambda x: x.get_name (),
00274                                     self.drivertypes),
00275                                pattern)
00276 
00277     def get_ordered_ppdnames (self, drivertypes, ppdsdict, fit):
00278         """
00279         Given a list of driver type names, a dict of PPD attributes by
00280         PPD name, and a dict of driver fitness status codes by PPD
00281         name, return a list of tuples in the form (driver-type-name,
00282         PPD-name), representing PPDs that match the list of driver
00283         types.
00284 
00285         The returned tuples will have driver types in the same order
00286         as the driver types given, with the exception that any
00287         blacklisted driver types will be omitted from the returned
00288         result.
00289         """
00290 
00291         ppdnames = []
00292 
00293         # First find out what driver types we have
00294         ppdtypes = {}
00295         fit_default = DriverType.FIT_CLOSE
00296         for ppd_name, ppd_dict in ppdsdict.iteritems ():
00297             drivertype = self.match (ppd_name, ppd_dict, fit.get (ppd_name,
00298                                                                   fit_default))
00299             if drivertype:
00300                 name = drivertype.get_name ()
00301             else:
00302                 name = "none"
00303 
00304             m = ppdtypes.get (name, [])
00305             m.append (ppd_name)
00306             ppdtypes[name] = m
00307 
00308         # Now construct the list.
00309         for drivertypename in drivertypes:
00310             for ppd_name in ppdtypes.get (drivertypename, []):
00311                 if ppd_name in ppdnames:
00312                     continue
00313 
00314                 ppdnames.append ((drivertypename, ppd_name))
00315 
00316         return ppdnames
00317 
00318 class PrinterType:
00319     """
00320     A make-and-model pattern and/or set of IEEE 1284 Device ID
00321     patterns for matching a set of printers, together with an ordered
00322     list of driver type names.
00323     """
00324 
00325     def __init__ (self):
00326         self.make_and_model = None
00327         self.deviceid = []
00328         self.drivertype_patterns = []
00329         self.avoid = set()
00330         self.blacklist = set()
00331 
00332     def add_make_and_model (self, pattern):
00333         """
00334         Set a make-and-model regular expression.  Only one is permitted.
00335         """
00336         self.make_and_model = re.compile (pattern, re.I)
00337 
00338     def add_deviceid_match (self, deviceid_match):
00339         """
00340         Add a Device ID match.
00341         """
00342         self.deviceid.append (deviceid_match)
00343 
00344     def add_drivertype_pattern (self, name):
00345         """
00346         Append a driver type pattern.
00347         """
00348         self.drivertype_patterns.append (name.strip ())
00349 
00350     def get_drivertype_patterns (self):
00351         """
00352         Return the list of driver type patterns.
00353         """
00354         return self.drivertype_patterns
00355 
00356     def add_avoidtype_pattern (self, name):
00357         """
00358         Add an avoid driver type pattern.
00359         """
00360         self.avoid.add (name)
00361 
00362     def get_avoidtype_patterns (self):
00363         """
00364         Return the set of driver type patterns to avoid.
00365         """
00366         return self.avoid
00367 
00368     def add_blacklisted (self, name):
00369         """
00370         Add a blacklisted driver type pattern.
00371         """
00372         self.blacklist.add (name)
00373 
00374     def get_blacklist (self):
00375         """
00376         Return the set of blacklisted driver type patterns.
00377         """
00378         return self.blacklist
00379 
00380     def match (self, make_and_model, deviceid):
00381         """
00382         Return True if there are no constraints to match against; if
00383         the make-and-model pattern matches; or if all of the IEEE 1284
00384         Device ID patterns match.
00385 
00386         The deviceid parameter must be a dict indexed by Device ID
00387         field key, of strings; except for the CMD field which must be
00388         a list of strings.
00389 
00390         Return False otherwise.
00391         """
00392 
00393         matches = (self.make_and_model == None and self.deviceid == [])
00394         if self.make_and_model:
00395             if self.make_and_model.match (make_and_model):
00396                 matches = True
00397 
00398         if not matches:
00399             for match in self.deviceid:
00400                 if match.match (deviceid):
00401                     matches = True
00402                     break
00403 
00404         return matches
00405 
00406 class PreferenceOrder:
00407     """
00408     A policy for choosing the preference order for drivers.
00409     """
00410 
00411     def __init__ (self):
00412         self.ptypes = []
00413 
00414     def load (self, preferreddrivers):
00415         """
00416         Load the policy from an XML file.
00417         """
00418 
00419         for printer in preferreddrivers.getchildren ():
00420             ptype = PrinterType ()
00421             for child in printer.getchildren ():
00422                 if child.tag == "make-and-model":
00423                     ptype.add_make_and_model (child.attrib["match"])
00424                 elif child.tag == "deviceid":
00425                     deviceid_match = DeviceIDMatch ()
00426                     for field in child.getchildren ():
00427                         if field.tag == "field":
00428                             deviceid_match.add_field (field.attrib["name"],
00429                                                       field.attrib["match"])
00430                     ptype.add_deviceid_match (deviceid_match)
00431 
00432                 elif child.tag == "drivers":
00433                     for drivertype in child.getchildren ():
00434                         ptype.add_drivertype_pattern (drivertype.text)
00435 
00436                 elif child.tag == "avoid":
00437                     for drivertype in child.getchildren ():
00438                         ptype.add_avoidtype_pattern (drivertype.text)
00439 
00440                 elif child.tag == "blacklist":
00441                     for drivertype in child.getchildren ():
00442                         ptype.add_blacklisted (drivertype.text)
00443 
00444             self.ptypes.append (ptype)
00445 
00446     def get_ordered_types (self, drivertypes, make_and_model, deviceid):
00447         """
00448         Return an accumulated list of driver types from all printer
00449         types that match a given printer's device-make-and-model and
00450         IEEE 1284 Device ID.
00451 
00452         The deviceid parameter must be None or a dict indexed by
00453         short-form upper-case field keys.
00454         """
00455 
00456         if deviceid == None:
00457             deviceid = {}
00458 
00459         if make_and_model == None:
00460             make_and_model = ""
00461 
00462         orderedtypes = []
00463         blacklist = set()
00464         avoidtypes = set()
00465         for ptype in self.ptypes:
00466             if ptype.match (make_and_model, deviceid):
00467                 for pattern in ptype.get_drivertype_patterns ():
00468                     # Match against the glob pattern
00469                     for drivertype in drivertypes.filter (pattern):
00470                         # Add each result if not already in the list.
00471                         if drivertype not in orderedtypes:
00472                             orderedtypes.append (drivertype)
00473 
00474                 for pattern in ptype.get_avoidtype_patterns ():
00475                     # Match against the glob pattern.
00476                     for drivertype in drivertypes.filter (pattern):
00477                         # Add each result to the set.
00478                         avoidtypes.add (drivertype)
00479 
00480                 for pattern in ptype.get_blacklist ():
00481                     # Match against the glob pattern.
00482                     for drivertype in drivertypes.filter (pattern):
00483                         # Add each result to the set.
00484                         blacklist.add (drivertype)
00485 
00486         if avoidtypes:
00487             avoided = []
00488             for t in avoidtypes:
00489                 try:
00490                     i = orderedtypes.index (t)
00491                     del orderedtypes[i]
00492                     avoided.append (t)
00493                 except IndexError:
00494                     continue
00495 
00496             orderedtypes.extend (avoided)
00497 
00498         if blacklist:
00499             # Remove blacklisted drivers.
00500             remaining = []
00501             for t in orderedtypes:
00502                 if t not in blacklist:
00503                     remaining.append (t)
00504 
00505             orderedtypes = remaining
00506 
00507         return orderedtypes
00508 
00509 
00510 def test (xml_path=None, attached=False, deviceid=None, debug=False):
00511     import cups
00512     import locale
00513     import ppds
00514     from pprint import pprint
00515     from time import time
00516     import os.path
00517 
00518     if debug:
00519         def debugprint (x):
00520             print x
00521 
00522         ppds.set_debugprint_fn (debugprint)
00523             
00524     locale.setlocale (locale.LC_ALL, "")
00525     encoding = locale.getlocale (locale.LC_CTYPE)[1]
00526     if xml_path == None:
00527         xml_path = os.path.join (os.path.join (os.path.dirname (__file__),
00528                                                ".."),
00529                                  "xml")
00530 
00531     os.environ["CUPSHELPERS_XMLDIR"] = xml_path
00532     xml_path = os.path.join (xml_path, "preferreddrivers.xml")
00533     loadstart = time ()
00534     (xmldrivertypes, xmlpreferenceorder) = PreferredDrivers (xml_path)
00535     drivertypes = DriverTypes ()
00536     drivertypes.load (xmldrivertypes)
00537 
00538     preforder = PreferenceOrder ()
00539     preforder.load (xmlpreferenceorder)
00540     loadtime = time () - loadstart
00541     if debug:
00542         print "Time to load %s: %.3fs" % (xml_path, loadtime)
00543 
00544     c = cups.Connection ()
00545     try:
00546         cupsppds = c.getPPDs2 ()
00547     except AttributeError:
00548         # getPPDs2 requires pycups >= 1.9.52 
00549         cupsppds = c.getPPDs ()
00550 
00551     ppdfinder = ppds.PPDs (cupsppds)
00552 
00553     if attached or deviceid:
00554         if attached:
00555             cups.setUser ("root")
00556             devices = c.getDevices ()
00557         else:
00558             devid = parseDeviceID (deviceid)
00559             devices = { "xxx://yyy":
00560                             { "device-id": deviceid,
00561                               "device-make-and-model": "%s %s" % (devid["MFG"],
00562                                                                   devid["MDL"])
00563                               }
00564                         }
00565 
00566         for uri, device in devices.iteritems ():
00567             if uri.find (":") == -1:
00568                 continue
00569 
00570             devid = device.get ("device-id", "")
00571             if isinstance (devid, list):
00572                 devid = devid[0]
00573 
00574             if not devid:
00575                 continue
00576 
00577             if not uri.startswith ("xxx:"):
00578                 print uri
00579 
00580             id_dict = parseDeviceID (devid)
00581             fit = ppdfinder.getPPDNamesFromDeviceID (id_dict["MFG"],
00582                                                      id_dict["MDL"],
00583                                                      id_dict["DES"],
00584                                                      id_dict["CMD"],
00585                                                      uri)
00586 
00587             mm = device.get ("device-make-and-model", "")
00588             orderedtypes = preforder.get_ordered_types (drivertypes,
00589                                                         mm, id_dict)
00590 
00591             ppds = {}
00592             for ppdname in fit.keys ():
00593                 ppds[ppdname] = ppdfinder.getInfoFromPPDName (ppdname)
00594 
00595             orderedppds = drivertypes.get_ordered_ppdnames (orderedtypes,
00596                                                             ppds,
00597                                                             fit)
00598             i = 1
00599             for t, ppd in orderedppds:
00600                 print "%d  %s\n    (%s, %s)" % (i, ppd, t, fit[ppd])
00601                 i += 1
00602     else:
00603         for make in ppdfinder.getMakes ():
00604             for model in ppdfinder.getModels (make):
00605                 ppdsdict = ppdfinder.getInfoFromModel (make, model)
00606                 mm = make + " " + model
00607                 orderedtypes = preforder.get_ordered_types (drivertypes,
00608                                                             mm, None)
00609 
00610                 fit = {}
00611                 for ppdname in ppdsdict.keys ():
00612                     fit[ppdname] = DriverType.FIT_CLOSE
00613 
00614                 orderedppds = drivertypes.get_ordered_ppdnames (orderedtypes,
00615                                                                 ppdsdict, fit)
00616                 print mm.encode (encoding) + ":"
00617                 i = 1
00618                 for t, ppd in orderedppds:
00619                     print "%d  %s\n    (%s)" % (i, ppd, t)
00620                     i += 1
00621 
00622                 print