Back to index

system-config-printer  1.3.9+20120706
cupshelpers.py
Go to the documentation of this file.
00001 ## system-config-printer
00002 
00003 ## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
00004 ## Authors:
00005 ##  Florian Festi <ffesti@redhat.com>
00006 ##  Tim Waugh <twaugh@redhat.com>
00007 
00008 ## This program is free software; you can redistribute it and/or modify
00009 ## it under the terms of the GNU General Public License as published by
00010 ## the Free Software Foundation; either version 2 of the License, or
00011 ## (at your option) any later version.
00012 
00013 ## This program is distributed in the hope that it will be useful,
00014 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 ## GNU General Public License for more details.
00017 
00018 ## You should have received a copy of the GNU General Public License
00019 ## along with this program; if not, write to the Free Software
00020 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00021 
00022 import cups, pprint, os, tempfile, re, string
00023 import locale
00024 from . import _debugprint
00025 
00026 class Printer:
00027     _flags_blacklist = ["options", "local"]
00028 
00029     def __init__(self, name, connection, **kw):
00030         """
00031         @param name: printer name
00032         @type name: string
00033         @param connection: CUPS connection
00034         @type connection: CUPS.Connection object
00035         @param kw: printer attributes
00036         @type kw: dict indexed by string
00037         """
00038         self.name = name
00039         self.connection = connection
00040         self.class_members = []
00041         have_kw = len (kw) > 0
00042         fetch_attrs = True
00043         if have_kw:
00044             self.update (**kw)
00045             if self.is_class:
00046                 fetch_attrs = True
00047             else:
00048                 fetch_attrs = False
00049 
00050         if fetch_attrs:
00051             self.getAttributes ()
00052 
00053         self._ppd = None # load on demand
00054 
00055     def __del__ (self):
00056         if self._ppd != None:
00057             os.unlink(self._ppd)
00058 
00059     def __repr__ (self):
00060         return "<cupshelpers.Printer \"%s\">" % self.name
00061 
00062     def _expand_flags(self):
00063 
00064         def _ascii_lower(str):
00065             return str.translate(string.maketrans(string.ascii_uppercase,
00066                                                   string.ascii_lowercase));
00067 
00068         prefix = "CUPS_PRINTER_"
00069         prefix_length = len(prefix)
00070 
00071         # loop over cups constants
00072         for name in cups.__dict__:
00073             if name.startswith(prefix):
00074                 attr_name = \
00075                     _ascii_lower(name[prefix_length:])
00076                 if attr_name in self._flags_blacklist: continue
00077                 if attr_name == "class": attr_name = "is_class"
00078                 # set as attribute
00079                 setattr(self, attr_name,
00080                         bool(self.type & getattr(cups, name)))
00081 
00082     def update(self, **kw):
00083         """
00084         Update object from printer attributes.
00085 
00086         @param kw: printer attributes
00087         @type kw: dict indexed by string
00088         """
00089         self.state = kw.get('printer-state', 0)
00090         self.enabled = self.state != cups.IPP_PRINTER_STOPPED
00091         self.device_uri = kw.get('device-uri', "")
00092         self.info = kw.get('printer-info', "")
00093         self.is_shared = kw.get('printer-is-shared', None)
00094         self.location = kw.get('printer-location', "")
00095         self.make_and_model = kw.get('printer-make-and-model', "")
00096         self.type = kw.get('printer-type', 0)
00097         self.uri_supported = kw.get('printer-uri-supported', "")
00098         if type (self.uri_supported) != list:
00099             self.uri_supported = [self.uri_supported]
00100         self._expand_flags()
00101         if self.is_shared is None:
00102             self.is_shared = not self.not_shared
00103         del self.not_shared
00104         self.class_members = kw.get('member-names', [])
00105         if type (self.class_members) != list:
00106             self.class_members = [self.class_members]
00107         self.class_members.sort ()
00108         self.other_attributes = kw
00109 
00110     def getAttributes(self):
00111         """
00112         Fetch further attributes for the printer.
00113 
00114         Normally only a small set of attributes is fetched.  This
00115         method is for fetching more.
00116         """
00117         attrs = self.connection.getPrinterAttributes(self.name)
00118         self.attributes = {}
00119         self.other_attributes = {}
00120         self.possible_attributes = {
00121             'landscape' : ('False', ['True', 'False']),
00122             'page-border' : ('none', ['none', 'single', 'single-thick',
00123                                      'double', 'double-thick']),
00124             }
00125 
00126         for key, value in attrs.iteritems():
00127             if key.endswith("-default"):
00128                 name = key[:-len("-default")]
00129                 if name in ["job-sheets", "printer-error-policy",
00130                             "printer-op-policy", # handled below
00131                             "notify-events", # cannot be set
00132                             "document-format", # cannot be set
00133                             "notify-lease-duration"]: # cannot be set
00134                     continue 
00135 
00136                 supported = attrs.get(name + "-supported", None) or \
00137                             self.possible_attributes.get(name, None) or \
00138                             ""
00139 
00140                 # Convert a list into a comma-separated string, since
00141                 # it can only really have been misinterpreted as a list
00142                 # by CUPS.
00143                 if isinstance (value, list):
00144                     value = reduce (lambda x, y: x+','+y, value)
00145 
00146                 self.attributes[name] = value
00147                     
00148                 if attrs.has_key(name+"-supported"):
00149                     supported = attrs[name+"-supported"]
00150                     self.possible_attributes[name] = (value, supported)
00151             elif (not key.endswith ("-supported") and
00152                   key != 'job-sheets-default' and
00153                   key != 'printer-error-policy' and
00154                   key != 'printer-op-policy' and
00155                   not key.startswith ('requesting-user-name-')):
00156                 self.other_attributes[key] = value
00157         
00158         self.job_sheet_start, self.job_sheet_end = attrs.get(
00159             'job-sheets-default', ('none', 'none'))
00160         self.job_sheets_supported = attrs.get('job-sheets-supported', ['none'])
00161         self.error_policy = attrs.get('printer-error-policy', 'none')
00162         self.error_policy_supported = attrs.get(
00163             'printer-error-policy-supported', ['none'])
00164         self.op_policy = attrs.get('printer-op-policy', "") or "default"
00165         self.op_policy_supported = attrs.get(
00166             'printer-op-policy-supported', ["default"])
00167 
00168         self.default_allow = True
00169         self.except_users = []
00170         if attrs.has_key('requesting-user-name-allowed'):
00171             self.except_users = attrs['requesting-user-name-allowed']
00172             self.default_allow = False
00173         elif attrs.has_key('requesting-user-name-denied'):
00174             self.except_users = attrs['requesting-user-name-denied']
00175         self.except_users_string = ', '.join(self.except_users)
00176         self.update (**attrs)
00177 
00178     def getServer(self):
00179         """
00180         Find out which server defines this printer.
00181 
00182         @returns: server URI or None
00183         """
00184         if not self.uri_supported[0].startswith('ipp://'):
00185             return None
00186         uri = self.uri_supported[0][6:]
00187         uri = uri.split('/')[0]
00188         uri = uri.split(':')[0]
00189         if uri == "localhost.localdomain":
00190             uri = "localhost"
00191         return uri
00192 
00193     def getPPD(self):
00194         """
00195         Obtain the printer's PPD.
00196 
00197         @returns: cups.PPD object, or False for raw queues
00198         @raise cups.IPPError: IPP error
00199         """
00200         result = None
00201         if self._ppd is None:
00202             try:
00203                 self._ppd = self.connection.getPPD(self.name)
00204                 result = cups.PPD (self._ppd)
00205             except cups.IPPError, (e, m):
00206                 if e == cups.IPP_NOT_FOUND:
00207                     result = False
00208                 else:
00209                     raise
00210 
00211         if result == None and self._ppd != None:
00212             result = cups.PPD (self._ppd)
00213 
00214         return result
00215 
00216     def setOption(self, name, value):
00217         """
00218         Set a printer's option.
00219 
00220         @param name: option name
00221         @type name: string
00222         @param value: option value
00223         @type value: option-specific
00224         """
00225         if isinstance (value, float):
00226             radixchar = locale.nl_langinfo (locale.RADIXCHAR)
00227             if radixchar != '.':
00228                 # Convert floats to strings, being careful with decimal points.
00229                 value = str (value).replace (radixchar, '.')
00230         self.connection.addPrinterOptionDefault(self.name, name, value)
00231 
00232     def unsetOption(self, name):
00233         """
00234         Unset a printer's option.
00235 
00236         @param name: option name
00237         @type name: string
00238         """
00239         self.connection.deletePrinterOptionDefault(self.name, name)
00240 
00241     def setEnabled(self, on, reason=None):
00242         """
00243         Set the printer's enabled state.
00244 
00245         @param on: whether it will be enabled
00246         @type on: bool
00247         @param reason: reason for this state
00248         @type reason: string
00249         """
00250         if on:
00251             self.connection.enablePrinter(self.name)
00252         else:
00253             if reason:
00254                 self.connection.disablePrinter(self.name, reason=reason)
00255             else:
00256                 self.connection.disablePrinter(self.name)
00257 
00258     def setAccepting(self, on, reason=None):
00259         """
00260         Set the printer's accepting state.
00261 
00262         @param on: whether it will be accepting
00263         @type on: bool
00264         @param reason: reason for this state
00265         @type reason: string
00266         """
00267         if on:
00268             self.connection.acceptJobs(self.name)
00269         else:
00270             if reason:
00271                 self.connection.rejectJobs(self.name, reason=reason)
00272             else:
00273                 self.connection.rejectJobs(self.name)
00274 
00275     def setShared(self,on):
00276         """
00277         Set the printer's shared state.
00278 
00279         @param on: whether it will be accepting
00280         @type on: bool
00281         """
00282         self.connection.setPrinterShared(self.name, on)
00283 
00284     def setErrorPolicy (self, policy):
00285         """
00286         Set the printer's error policy.
00287 
00288         @param policy: error policy
00289         @type policy: string
00290         """
00291         self.connection.setPrinterErrorPolicy(self.name, policy)
00292 
00293     def setOperationPolicy(self, policy):
00294         """
00295         Set the printer's operation policy.
00296 
00297         @param policy: operation policy
00298         @type policy: string
00299         """
00300         self.connection.setPrinterOpPolicy(self.name, policy)    
00301 
00302     def setJobSheets(self, start, end):
00303         """
00304         Set the printer's job sheets.
00305 
00306         @param start: start sheet
00307         @type start: string
00308         @param end: end sheet
00309         @type end: string
00310         """
00311         self.connection.setPrinterJobSheets(self.name, start, end)
00312 
00313     def setAccess(self, allow, except_users):
00314         """
00315         Set access control list.
00316 
00317         @param allow: whether to allow by default, otherwise deny
00318         @type allow: bool
00319         @param except_users: exception list
00320         @type except_users: string list
00321         """
00322         if isinstance(except_users, str):
00323             users = except_users.split()
00324             users = [u.split(",") for u in users]
00325             except_users = []
00326             for u in users:
00327                 except_users.extend(u)
00328             except_users = [u.strip() for u in except_users]
00329             except_users = filter(None, except_users)
00330             
00331         if allow:
00332             self.connection.setPrinterUsersDenied(self.name, except_users)
00333         else:
00334             self.connection.setPrinterUsersAllowed(self.name, except_users)
00335 
00336     def jobsQueued(self, only_tests=False, limit=None):
00337         """
00338         Find out whether jobs are queued for this printer.
00339 
00340         @param only_tests: whether to restrict search to test pages
00341         @type only_tests: bool
00342         @returns: list of job IDs
00343         """
00344         ret = []
00345         try:
00346             try:
00347                 r = ['job-id', 'job-printer-uri', 'job-name']
00348                 jobs = self.connection.getJobs (requested_attributes=r)
00349             except TypeError:
00350                 # requested_attributes requires pycups 1.9.50
00351                 jobs = self.connection.getJobs ()
00352         except cups.IPPError:
00353             return ret
00354 
00355         for id, attrs in jobs.iteritems():
00356             try:
00357                 uri = attrs['job-printer-uri']
00358                 uri = uri[uri.rindex ('/') + 1:]
00359             except:
00360                 continue
00361             if uri != self.name:
00362                 continue
00363 
00364             if (not only_tests or
00365                 (attrs.has_key ('job-name') and
00366                  attrs['job-name'] == 'Test Page')):
00367                 ret.append (id)
00368 
00369                 if limit != None and len (ret) == limit:
00370                     break
00371         return ret
00372 
00373     def jobsPreserved(self, limit=None):
00374         """
00375         Find out whether there are preserved jobs for this printer.
00376 
00377         @return: list of job IDs
00378         """
00379         ret = []
00380         try:
00381             try:
00382                 r = ['job-id', 'job-printer-uri', 'job-state']
00383                 jobs = self.connection.getJobs (which_jobs='completed',
00384                                                 requested_attributes=r)
00385             except TypeError:
00386                 # requested_attributes requires pycups 1.9.50
00387                 jobs = self.connection.getJobs (which_jobs='completed')
00388         except cups.IPPError:
00389             return ret
00390 
00391         for id, attrs in jobs.iteritems():
00392             try:
00393                 uri = attrs['job-printer-uri']
00394                 uri = uri[uri.rindex ('/') + 1:]
00395             except:
00396                 continue
00397             if uri != self.name:
00398                 continue
00399             if (attrs.get ('job-state',
00400                            cups.IPP_JOB_PENDING) < cups.IPP_JOB_COMPLETED):
00401                 continue
00402             ret.append (id)
00403             if limit != None and len (ret) == limit:
00404                 break
00405 
00406         return ret
00407 
00408     def testsQueued(self, limit=None):
00409         """
00410         Find out whether test jobs are queued for this printer.
00411 
00412         @returns: list of job IDs
00413         """
00414         return self.jobsQueued (only_tests=True, limit=limit)
00415 
00416     def setAsDefault(self):
00417         """
00418         Set this printer as the system default.
00419         """
00420         self.connection.setDefault(self.name)
00421 
00422         # Also need to check system-wide lpoptions because that's how
00423         # previous Fedora versions set the default (bug #217395).
00424         (tmpfd, tmpfname) = tempfile.mkstemp ()
00425         os.remove (tmpfname)
00426         try:
00427             resource = "/admin/conf/lpoptions"
00428             self.connection.getFile(resource, fd=tmpfd)
00429         except cups.HTTPError, (s,):
00430             if s == cups.HTTP_NOT_FOUND:
00431                 return False
00432 
00433             raise cups.HTTPError (s)
00434 
00435         f = os.fdopen (tmpfd, 'r+')
00436         f.seek (0)
00437         lines = f.readlines ()
00438         changed = False
00439         i = 0
00440         for line in lines:
00441             if line.startswith ("Default "):
00442                 # This is the system-wide default.
00443                 name = line.split (' ')[1]
00444                 if name != self.name:
00445                     # Stop it from over-riding the server default.
00446                     lines[i] = "Dest " + line[8:]
00447                     changed = True
00448                 i += 1
00449 
00450         if changed:
00451             f.seek (0)
00452             f.writelines (lines)
00453             f.truncate ()
00454             os.lseek (tmpfd, 0, os.SEEK_SET)
00455             try:
00456                 self.connection.putFile (resource, fd=tmpfd)
00457             except cups.HTTPError, (s,):
00458                 return False
00459 
00460         return changed
00461 
00462 def getPrinters(connection):
00463     """
00464     Obtain a list of printers.
00465 
00466     @param connection: CUPS connection
00467     @type connection: CUPS.Connection object
00468     @returns: L{Printer} list
00469     """
00470     printers = connection.getPrinters()
00471     classes = connection.getClasses()
00472     for name, printer in printers.iteritems():
00473         printer = Printer(name, connection, **printer)
00474         printers[name] = printer
00475         if classes.has_key(name):
00476             printer.class_members = classes[name]
00477             printer.class_members.sort()
00478     return printers
00479 
00480 def parseDeviceID (id):
00481     """
00482     Parse an IEEE 1284 Device ID, so that it may be indexed by field name.
00483 
00484     @param id: IEEE 1284 Device ID, without the two leading length bytes
00485     @type id: string
00486     @returns: dict indexed by field name
00487     """
00488     id_dict = {}
00489     pieces = id.split(";")
00490     for piece in pieces:
00491         if piece.find(":") == -1:
00492             continue
00493         name, value = piece.split(":",1)
00494         id_dict[name.strip ()] = value.strip()
00495     if id_dict.has_key ("MANUFACTURER"):
00496         id_dict.setdefault("MFG", id_dict["MANUFACTURER"])
00497     if id_dict.has_key ("MODEL"):
00498         id_dict.setdefault("MDL", id_dict["MODEL"])
00499     if id_dict.has_key ("COMMAND SET"):
00500         id_dict.setdefault("CMD", id_dict["COMMAND SET"])
00501     for name in ["MFG", "MDL", "CMD", "CLS", "DES", "SN", "S", "P", "J"]:
00502         id_dict.setdefault(name, "")
00503     if id_dict["CMD"] == '':
00504         id_dict["CMD"] = []
00505     else:
00506         id_dict["CMD"] = id_dict["CMD"].split(',') 
00507     return id_dict
00508 
00509 class Device:
00510     """
00511     This class represents a CUPS device.
00512     """
00513 
00514     def __init__(self, uri, **kw):
00515         """
00516         @param uri: device URI
00517         @type uri: string
00518         @param kw: device attributes
00519         @type kw: dict
00520         """
00521         self.uri = uri
00522         self.device_class = kw.get('device-class', '')
00523         self.info = kw.get('device-info', '')
00524         self.make_and_model = kw.get('device-make-and-model', '')
00525         self.id = kw.get('device-id', '')
00526         self.location = kw.get('device-location', '')
00527 
00528         uri_pieces = uri.split(":")
00529         self.type =  uri_pieces[0]
00530         self.is_class = len(uri_pieces)==1
00531 
00532         #self.id = 'MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;DES:Hewlett-Packard DeskJet 990C;SN:US05N1J00XLG;S:00808880800010032C1000000C2000000;P:0800,FL,B0;J:                    ;'
00533 
00534         self.id_dict = parseDeviceID (self.id)
00535 
00536         s = uri.find("serial=")
00537         if s != -1 and not self.id_dict.get ('SN',''):
00538             self.id_dict['SN'] = uri[s + 7:]
00539 
00540     def __repr__ (self):
00541         return "<cupshelpers.Device \"%s\">" % self.uri
00542 
00543     def __cmp__(self, other):
00544         """
00545         Compare devices by order of preference.
00546         """
00547         if other == None:
00548             return -1
00549 
00550         if self.is_class != other.is_class:
00551             if other.is_class:
00552                 return -1
00553             return 1
00554         if not self.is_class and (self.type != other.type):
00555             # "hp"/"hpfax" before "usb" before * before "parallel" before
00556             # "serial"
00557             if other.type == "serial":
00558                 return -1
00559             if self.type == "serial":
00560                 return 1
00561             if other.type == "parallel":
00562                 return -1
00563             if self.type == "parallel":
00564                 return 1
00565             if other.type == "hp" or other.type == "hpfax":
00566                 return 1
00567             if self.type == "hp" or self.type == "hpfax":
00568                 return -1
00569             if other.type == "dnssd":
00570                 return 1
00571             if self.type == "dnssd":
00572                 return -1
00573             if other.type == "usb":
00574                 return 1
00575             if self.type == "usb":
00576                 return -1
00577         result = cmp(bool(self.id), bool(other.id))
00578         if not result:
00579             result = cmp(self.info, other.info)
00580         
00581         return result
00582 
00583 class _GetDevicesCall(object):
00584     def call (self, connection, kwds):
00585         if kwds.has_key ("reply_handler"):
00586             self._client_reply_handler = kwds.get ("reply_handler")
00587             kwds["reply_handler"] = self._reply_handler
00588             return connection.getDevices (**kwds)
00589 
00590         self._client_reply_handler = None
00591         result = connection.getDevices (**kwds)
00592         return self._reply_handler (connection, result)
00593 
00594     def _reply_handler (self, connection, devices):
00595         for uri, data in devices.iteritems():
00596             device = Device(uri, **data)
00597             devices[uri] = device
00598             if device.info != '' and device.make_and_model == '':
00599                 device.make_and_model = device.info
00600 
00601         if self._client_reply_handler:
00602             self._client_reply_handler (connection, devices)
00603         else:
00604             return devices
00605             
00606 def getDevices(connection, **kw):
00607     """
00608     Obtain a list of available CUPS devices.
00609 
00610     @param connection: CUPS connection
00611     @type connection: cups.Connection object
00612     @returns: a list of L{Device} objects
00613     @raise cups.IPPError: IPP Error
00614     """
00615     op = _GetDevicesCall ()
00616     return op.call (connection, kw)
00617 
00618 def activateNewPrinter(connection, name):
00619     """
00620     Set a new printer enabled, accepting jobs, and (if necessary) the
00621     default printer.
00622 
00623     @param connection: CUPS connection
00624     @type connection: cups.Connection object
00625     @param name: printer name
00626     @type name: string
00627     @raise cups.IPPError: IPP error
00628     """
00629     connection.enablePrinter (name)
00630     connection.acceptJobs (name)
00631 
00632     # Set as the default if there is not already a default printer.
00633     if connection.getDefault () == None:
00634         connection.setDefault (name)
00635 
00636 def copyPPDOptions(ppd1, ppd2):
00637     """
00638     Copy default options between PPDs.
00639 
00640     @param ppd1: source PPD
00641     @type ppd1: cups.PPD object
00642     @param ppd2: destination PPD
00643     @type ppd2: cups.PPD object
00644     """
00645     def getPPDGroupOptions(group):
00646        options = group.options[:]
00647         for g in group.subgroups:
00648             options.extend(getPPDGroupOptions(g))
00649         return options
00650 
00651     def iteratePPDOptions(ppd):
00652        for group in ppd.optionGroups:
00653             for option in getPPDGroupOptions(group):
00654               yield option
00655 
00656     for option in iteratePPDOptions(ppd1):
00657         if option.keyword == "PageRegion":
00658             continue
00659         new_option = ppd2.findOption(option.keyword)
00660         if new_option and option.ui==new_option.ui:
00661             value = option.defchoice
00662             for choice in new_option.choices:
00663                 if choice["choice"]==value:
00664                     ppd2.markOption(new_option.keyword, value)
00665                     _debugprint ("set %s = %s" % (new_option.keyword, value))
00666                     
00667 def setPPDPageSize(ppd, language):
00668     """
00669     Set the PPD page size according to locale.
00670 
00671     @param ppd: PPD
00672     @type ppd: cups.PPD object
00673     @param language: language, as given by the first element of
00674     locale.setlocale
00675     @type language: string
00676     """
00677     # Just set the page size to A4 or Letter, that's all.
00678     # Use the same method CUPS uses.
00679     size = 'A4'
00680     letter = [ 'C', 'POSIX', 'en', 'en_US', 'en_CA', 'fr_CA' ]
00681     for each in letter:
00682         if language == each:
00683             size = 'Letter'
00684     try:
00685         ppd.markOption ('PageSize', size)
00686         _debugprint ("set PageSize = %s" % size)
00687     except:
00688         _debugprint ("Failed to set PageSize (%s not available?)" % size)
00689 
00690 def missingExecutables(ppd):
00691     """
00692     Check that all relevant executables for a PPD are installed.
00693 
00694     @param ppd: PPD
00695     @type ppd: cups.PPD object
00696     @returns: string list, representing missing executables
00697     """
00698 
00699     # First, a local function.  How to check that something exists
00700     # in a path:
00701     def pathcheck (name, path="/usr/bin:/bin"):
00702         if name == "-":
00703             # A filter of "-" means that no filter is required,
00704             # i.e. the device accepts the given format as-is.
00705             return "builtin"
00706         # Strip out foomatic '%'-style place-holders.
00707         p = name.find ('%')
00708         if p != -1:
00709             name = name[:p]
00710         if len (name) == 0:
00711             return "true"
00712         if name[0] == '/':
00713             if os.access (name, os.X_OK):
00714                 _debugprint ("%s: found" % name)
00715                 return name
00716             else:
00717                 _debugprint ("%s: NOT found" % name)
00718                 return None
00719         if name.find ("=") != -1:
00720             return "builtin"
00721         if name in [ ":", ".", "[", "alias", "bind", "break", "cd",
00722                      "continue", "declare", "echo", "else", "eval",
00723                      "exec", "exit", "export", "fi", "if", "kill", "let",
00724                      "local", "popd", "printf", "pushd", "pwd", "read",
00725                      "readonly", "set", "shift", "shopt", "source",
00726                      "test", "then", "trap", "type", "ulimit", "umask",
00727                      "unalias", "unset", "wait" ]:
00728             return "builtin"
00729         for component in path.split (':'):
00730             file = component.rstrip (os.path.sep) + os.path.sep + name
00731             if os.access (file, os.X_OK):
00732                 _debugprint ("%s: found" % file)
00733                 return file
00734         _debugprint ("%s: NOT found in %s" % (name,path))
00735         return None
00736 
00737     exes_to_install = []
00738 
00739     def add_missing (exe):
00740         # Strip out foomatic '%'-style place-holders.
00741         p = exe.find ('%')
00742         if p != -1:
00743             exe = exe[:p]
00744 
00745         exes_to_install.append (exe)
00746 
00747     # Find a 'FoomaticRIPCommandLine' attribute.
00748     exe = exepath = None
00749     attr = ppd.findAttr ('FoomaticRIPCommandLine')
00750     if attr:
00751         # Foomatic RIP command line to check.
00752         cmdline = attr.value.replace ('&&\n', '')
00753         cmdline = cmdline.replace ('&quot;', '"')
00754         cmdline = cmdline.replace ('&lt;', '<')
00755         cmdline = cmdline.replace ('&gt;', '>')
00756         if (cmdline.find ("(") != -1 or
00757             cmdline.find ("&") != -1):
00758             # Don't try to handle sub-shells or unreplaced HTML entities.
00759             cmdline = ""
00760 
00761         # Strip out foomatic '%'-style place-holders
00762         pipes = cmdline.split (';')
00763         for pipe in pipes:
00764             cmds = pipe.strip ().split ('|')
00765             for cmd in cmds:
00766                 args = cmd.strip ().split (' ')
00767                 exe = args[0]
00768                 exepath = pathcheck (exe)
00769                 if not exepath:
00770                     add_missing (exe)
00771                     continue
00772 
00773                 # Main executable found.  But if it's 'gs',
00774                 # perhaps there is an IJS server we also need
00775                 # to check.
00776                 if os.path.basename (exepath) == 'gs':
00777                     argn = len (args)
00778                     argi = 1
00779                     search = "-sIjsServer="
00780                     while argi < argn:
00781                         arg = args[argi]
00782                         if arg.startswith (search):
00783                             exe = arg[len (search):]
00784                             exepath = pathcheck (exe)
00785                             if not exepath:
00786                                 add_missing (exe)
00787 
00788                             break
00789 
00790                         argi += 1
00791 
00792             if not exepath:
00793                 # Next pipe.
00794                 break
00795 
00796     if exepath or not exe:
00797         # Look for '*cupsFilter' lines in the PPD and check that
00798         # the filters are installed.
00799         (tmpfd, tmpfname) = tempfile.mkstemp ()
00800         os.unlink (tmpfname)
00801         ppd.writeFd (tmpfd)
00802         os.lseek (tmpfd, 0, os.SEEK_SET)
00803         f = os.fdopen (tmpfd, "r")
00804         search = "*cupsFilter:"
00805         for line in f.readlines ():
00806             if line.startswith (search):
00807                 line = line[len (search):].strip ().strip ('"')
00808                 try:
00809                     (mimetype, cost, exe) = line.split (' ')
00810                 except:
00811                     continue
00812 
00813                 exepath = pathcheck (exe,
00814                                      "/usr/lib/cups/filter:"
00815                                      "/usr/lib64/cups/filter")
00816                 if not exepath:
00817                     add_missing ("/usr/lib/cups/filter/" + exe)
00818 
00819     return exes_to_install
00820 
00821 def missingPackagesAndExecutables(ppd):
00822     """
00823     Check that all relevant executables for a PPD are installed.
00824 
00825     @param ppd: PPD
00826     @type ppd: cups.PPD object
00827     @returns: string list pair, representing missing packages and
00828     missing executables
00829     """
00830     executables = missingExecutables(ppd)
00831     return ([], executables)
00832 
00833 def _main():
00834     c = cups.Connection()
00835     #printers = getPrinters(c)
00836     for device in getDevices(c).itervalues():
00837         print device.uri, device.id_dict
00838 
00839 if __name__=="__main__":
00840     _main()