#!/usr/bin/python # $Id$ # Update CUPS PPDs for Gutenprint queues. # Copyright (C) 2002-2003 Roger Leigh (rleigh@debian.org) # Copyright (C) 2009, 2011 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import getopt import glob import os import re import stat import subprocess import sys global optargs global debug global verbose global interactive global quiet global no_action global reset_defaults global version global micro_version global use_static_ppd global file_version global ppd_dir global ppd_root_dir global ppd_base_dir global ppd_out_dir global gzext global updated_ppd_count global skipped_ppd_count global failed_ppd_count global exit_after_parse_args global languages global serverdir global driver_bin global driver_version global server_multicat global server_multicat_initialized global ppd_files global languagemappings def help(): print """ Usage: %s [OPTION]... [PPD_FILE]... Update CUPS+Gutenprint PPD files. -d flags Enable debugging -h Display this help text -n No-action. Don't overwrite any PPD files. -q Quiet mode. No messages except errors. -s ppd_dir Use ppd_dir as the source PPD directory. -p ppd_dir Update PPD files in ppd_dir. -P driver Use the specified driver binary to generate PPD files. -v Verbose messages. -N Reset options to defaults. -o out_dir Output PPD files to out_dir. -r version Use PPD files for Gutenprint major.minor version. -f Ignore new PPD file safety checks. -i Prompt (interactively) for each PPD file. -l language Language choice (Gutenprint 5.1 or below). Choices: %s Or -loriginal to preserve original language with Gutenprint 5.2 or above """ % (sys.argv[0], reduce (lambda x,y: "%s %s" % (x,y), languages)) sys.exit (0) def die_if_not_directory (dir): try: st = os.stat (dir) if not stat.S_ISDIR (st.st_mode): os.chdir (dir) except OSError, (e, s): print "%s: invalid directory: %s" % (dir, s) sys.exit (1) def get_driver_version(): global server_multicat global driver_version def run_with_arg (arg): try: p = subprocess.Popen ([driver_bin, arg], stdin=file("/dev/null"), stdout=subprocess.PIPE, stderr=file("/dev/null", "w"), shell=False) (stdout, stderr) = p.communicate () except OSError: return None return stdout stdout = run_with_arg ("org.gutenprint.extensions") if stdout == None: return for line in stdout.split ("\n"): if line == "org.gutenprint.multicat": server_multicat = 1 break stdout = run_with_arg ("VERSION") if stdout == None: return driver_version = stdout.strip () def parse_options(): try: opts, args = getopt.getopt (sys.argv[1:], "d:hnqs:vNo:p:P:r:ifl:") except getopt.GetoptError: help () global optargs global debug global verbose global interactive global quiet global no_action global reset_defaults global version global micro_version global use_static_ppd global file_version global ppd_dir global ppd_out_dir global ppd_base_dir global ppd_root_dir global serverdir global driver_bin global driver_version global server_multicat global languages optargs = dict() for opt, optarg in opts: optargs[opt[1]] = optarg if optargs.has_key ('n'): no_action = 1 if optargs.has_key ('d'): try: debug = int (optargs['d']) except ValueError: d = 0 if optargs.has_key ('v'): verbose = 1 quiet = 0 if optargs.has_key ('q'): verbose = 0 quiet = 1 if optargs.has_key ('N'): reset_defaults = 1 if optargs.has_key ('o'): opt_o = optargs['o'] die_if_not_directory (opt_o) ppd_out_dir = opt_o if optargs.has_key ('r'): opt_r = optargs['r'] if version != opt_r: version = opt_r if optargs.has_key ('s'): opt_s = optargs['s'] die_if_not_directory (opt_s) ppd_base_dir = opt_s driver_bin = "" server_multicat = 0 use_static_ppd = "yes" else: ppd_base_dir = ppd_root_dir + "/gutenprint/" + version driver_bin = serverdir + "/driver/gutenprint." + version driver_version = "" # If user specifies version, we're not going to be able to check # for an exact match. file_version = '"' + version if os.access (driver_bin, os.X_OK): get_driver_version () use_static_ppd = "no" file_version = "\"%s\"$" % driver_version else: print "Gutenprint %s does not appear to be installed!" % version sys.exit (1) if optargs.has_key ('s'): opt_s = optargs['s'] die_if_not_directory (opt_s) ppd_base_dir = opt_s driver_bin = "" server_multicat = 0 driver_version = "" use_static_ppd = "yes" if optargs.has_key ('p'): opt_p = optargs['p'] die_if_not_directory (opt_p) ppd_dir = opt_p if optargs.has_key ('P'): opt_P = optargs['P'] if os.access (opt_P, os.X_OK): driver_bin = opt_P get_driver_version () use_static_ppd = "no" else: print "%s: invalid executable" % opt_P if optargs.has_key ('h'): help () if (optargs.has_key ('l') and optargs['l'].lower () != "original" and optargs['l'].lower () not in languages): print >>sys.stderr, "Unknown language '%s'" % optargs['l'] if optargs.has_key ('i'): interactive = 1 if exit_after_parse_args: sys.exit (0) if verbose and driver_version != "": print "Updating PPD files from Gutenprint %s" % driver_version return args def update_ppd (ppd_source_filename): global ppd_dest_filename global ppd_out_dir global optargs global languagemappings global interactive global server_multicat global no_action global quiet, verbose global reset_defaults ppd_dest_filename = ppd_source_filename if ppd_out_dir: ppd_dest_filename = "%s/%s" % (ppd_out_dir, os.path.basename (ppd_dest_filename)) orig = file (ppd_source_filename) orig_metadata = os.fstat (orig.fileno ()) if debug & 1: print "Source Filename: %s" % ppd_source_filename filename = "" driver = "" gutenprintdriver = "" locale = "" lingo = "" region = "" valid = 0 orig_locale = "" for line in orig.readlines (): line.rstrip () if line.find ("*StpLocale:") != -1: match = re.search ("\*StpLocale:\s*\"(.*)\"$", line) if match: groups = match.groups () if len (groups) >= 1: locale = groups[0] orig_locale = locale valid = 1 elif line.startswith ("*LanguageVersion"): match = re.search ("^\*LanguageVersion:\s*(.*)$", line) if match: groups = match.groups () if len (groups) >= 1: lingo = groups[0] elif line.startswith ("*StpDriverName:"): match = re.search ("^\*StpDriverName:\s*\"(.*)\"$", line) if match: groups = match.groups () if len (groups) >= 1: driver = groups[0] valid = 1 elif line.find ("*%End of ") != -1 and driver == "": match = re.search ("^\*%End of\s*(.*).ppd$", line) if match: groups = match.groups () if len (groups) >= 1: driver = groups[0] elif line.startswith ("*StpPPDLocation:"): match = re.search ("^\*StpPPDLocation:\s*\"(.*)\"$", line) if match: groups = match.groups () if len (groups) >= 1: filename = groups[0] valid = 1 elif line.startswith ("*%Gutenprint Filename:"): valid = 1 if filename and driver and lingo and locale: break if not valid and line.startswith ("*OpenUI"): break if not valid: #print >>sys.stderr, ("Skipping %s: not a Gutenprint PPD file" % # ppd_source_filename) return -1 if (optargs.has_key ('l') and optargs['l'] != "" and optargs['l'].lower () != "original"): locale = optargs['l'] orig_locale = locale if debug & 2: print "Gutenprint Filename: %s" % filename if optargs.has_key ('l'): print "Locale: %s (from -l)" % locale else: print "Locale: %s" % locale print "Language: %s" % lingo print "Driver: %s" % driver if locale: # Split into the language and territory. s = locale.split ("_", 1) locale = s[0] try: region = s[1] except IndexError: region = "" else: # Split into the language and territory. s = lingo.split ("_", 1) locale = s[0] try: region = s[1] except IndexError: region = "" # Convert language into language code. locale = languagemappings.get (lingo.lower (), "C") if debug & 2: print "Base Locale: %s" % locale print "Region: %s" % region # Read in the new PPD, decompressing it if needed... (new_ppd_filename, source_fd) = get_ppd_fh (ppd_source_filename, filename, driver, locale, region) if source_fd == None: print "Unable to retrieve PPD file!" return 0 if interactive: inp = raw_input ("Update PPD %s from %s [nyq]? " % ppd_source_filename) inp = inp.lower () if inp.startswith ("q"): if not server_multicat: source_fd.close () print "Skipping all..." return -2 elif not inp.startswith ("y"): if not server_multicat: source_fd.close () print "Skipping..." return -1 # Extract the default values from the original PPD... orig.seek (0) (odt, oopt, ores, odef, unused) = get_ppd_data (orig, 1, 0, 1, 1, 0) (ndt, nopt, nres, ndef, source_data) = get_ppd_data (source_fd, 1, 1, 1, 1, 1) # Close original and temporary files... orig.close () if not server_multicat: source_fd.close () orig_default_types = odt new_default_types = ndt defaults = odef new_defaults = ndef options = nopt resolution_map = nres old_resolution_map = dict() for key, value in resolution_map.iteritems (): old_resolution_map[value] = key # Store previous language in the PPD file so that -l original works # correctly. if orig_locale != "": lines = source_data.rstrip ().split ("\n") source_data = "" for line in lines: m = re.search ("(\*StpLocale:\s*\")(.*)(\")", line) if m: groups = m.groups () line = groups[0] + orig_locale + groups[2] source_data += line + "\n" if debug & 4: print "Options (Old->New Default Type):" keys = options.keys () keys.sort () for t in keys: old_type = orig_default_types.get (t, "(New)") new_type = new_default_types.get (t) if old_type != new_type: out = " %s (%s -> %s) : " % (t, old_type, new_type) else: out = " %s (%s) : " % (t, new_type) dft = defaults.get ("Default%s" % t) for opt in options.get (t, []): if dft != None and dft == opt: out += "*" out += "%s " % opt print out if len (resolution_map.keys ()) > 0: print "Resolution Map:" keys = resolution_map.keys () keys.sort () for key in keys: print " %s: %s" % (key, resolution_map[key]) if len (old_resolution_map.keys ()) > 0: print "Old Resolution Map:" keys = old_resolution_map.keys () keys.sort () for key in keys: print " %s: %s" % (key, old_resolution_map[key]) print "Non-UI Defaults:" keys = defaults.keys () keys.sort () for key in keys: xkey = key if xkey.startswith ("Default"): xkey = xkey[7:] if not options.has_key (xkey): print " %s: %s" % (key, defaults[key]) print "Default Types of dropped options:" keys = orig_default_types.keys () keys.sort () for t in keys: if not options.has_key (t): print " %s: %s" % (t, orig_default_types[t]) if no_action: if not quiet or verbose: if ppd_dest_filename == ppd_source_filename: print "Would update %s using %s" % (ppd_source_filename, new_ppd_filename) else: print "Would update %s to %s using %s" % (ppd_source_filename, ppd_dest_filename, new_ppd_filename) return 0 if not reset_defaults: # Update source buffer with old defaults... # Loop through each default in turn. keys = defaults.keys () keys.sort () for default_option in keys: default_option_value = defaults[default_option] option = default_option if option.startswith ("Default"): # Strip off `Default' option = option[7:] # Check method is valid orig_method = orig_default_types.get (option) new_method = new_default_types.get (option) new_default = new_defaults.get (default_option) if (orig_method == None or new_method == None or orig_method != new_method): continue if (new_default != None and default_option_value == new_default): if verbose: print "%s: Preserve *%s (%s)" % (ppd_source_filename, default_option, default_option_value) continue if new_method == "PickOne": next_default = False # Check the old setting is valid for opt in options.get (option, []): def_option = default_option_value odef_option = def_option if (option == "Resolution" and old_resolution_map.has_key (def_option)): if debug & 4: print ("Intermapping old resolution %s to %s" % def_option, old_resolution_map[def_option]) def_option = old_resolution_map[def_option] dopts = [def_option] if def_option != odef_option: dopts.append (odef_option) for dopt in dopts: valid = False if dopt == opt: valid = True elif (option == "Resolution" and resolution_map.has_key (dopt)): dopt = resolution_map[dopt] if dopt == opt: valid = True if valid: # Valid option # Set the option in the new PPD lines = source_data.rstrip ().split ("\n") source_data = "" attr = "*%s" % default_option for line in lines: if line.startswith (attr): line = "%s:%s" % (attr, dopt) source_data += line + "\n" if verbose: print "%s: Set *%s to %s" % (ppd_source_filename, default_option, dopt) next_default = True break if next_default: break if next_default: continue print ("Warning: %s: Invalid option: *%s: %s. Using default " "setting %s." % (ppd_source_filename, default_option, defaults[default_option], new_defaults[default_option])) continue print ("Warning: %s: PPD OpenUI method %s not understood." % (ppd_source_filename, new_default_types[default_option])) # Write new PPD... tmpnew = "%s.new" % ppd_dest_filename try: newppd = file (tmpnew, "w") except IOError, (e, s): print "Can't create %s: %s" % (tmpnew, s) return 0 newppd.writelines (source_data) try: newppd.close () except IOError, (e, s): print "Can't write to %s: %s" % (tmpnew, s) return 0 chcon = subprocess.Popen (["chcon", "--reference=%s" % ppd_dest_filename, tmpnew], shell=False, stdin=file("/dev/null"), stdout=file("/dev/null", "w"), stderr=subprocess.STDOUT) chcon.communicate () try: os.rename (tmpnew, ppd_dest_filename) except OSError, (e, s): print "Can't rename %s to %s: %s" % (tmpnew, ppd_dest_filename, s) try: os.unlink (tmpnew) except OSError: pass return 0 try: os.chown (ppd_dest_filename, orig_metadata.st_uid, orig_metadata.st_gid) except OSError: pass try: os.chmod (ppd_dest_filename, orig_metadata.st_mode & 0777) except OSError: pass if not quiet or verbose: if ppd_dest_filename == ppd_source_filename: print "Updated %s using %s" % (ppd_source_filename, new_ppd_filename) else: print "Updated %s to %s using %s" % (ppd_source_filename, ppd_dest_filename, new_ppd_filename) # All done! return 1 def get_ppd_data (fh, types, opts, resolutions, defaults, data): options_map = dict() defaults_map = dict() resolution_map = dict() default_types = dict() cur_opt = "" optionlist = [] source_data = "" if reset_defaults: types = 0 opts = 0 resolutions = 0 defaults = 0 if resolutions or types or opts or defaults or data: while True: line = fh.readline () if line == '': break if line == "*%*%EOFEOF\n": break source_data += line line = line.strip () if (types or opts) and line.startswith ("*OpenUI"): m = re.search ("^\*OpenUI\s\*(\w+).*:\s(\w+)", line) if m: groups = m.groups () key = groups[0] value = groups[1] default_types[key] = value cur_opt = key elif opts and line.startswith ("*CloseUI"): if cur_opt != "": options_map[cur_opt] = optionlist cur_opt = "" optionlist = [] elif opts and line.startswith ("*%s" % cur_opt): m = re.search ("^\*%s\s*(\w+)[\/:]" % cur_opt, line) if m: groups = m.groups() if len (groups) >= 1: value = m.groups ()[0] optionlist.append (value) elif resolutions and line.startswith ("*StpResolutionMap:"): s = line.split (None, 3) if len (s) == 3: new = s[1] old = s[2] resolution_map[old] = new elif defaults and line.startswith ("*Default"): m = re.search ("^\*(\w+):\s*(\w+)", line) if m: groups = m.groups () key = groups[0] value = groups[1] defaults_map[key] = value return (default_types, options_map, resolution_map, defaults_map, source_data) def get_ppd_fh (ppd_source_filename, filename, driver, locale, region): global use_static_ppd global driver_version global optargs global driver_bin global debug global server_multicat, server_multicat_initialized global gzext if use_static_ppd == "no" and driver_version != "": if re.search (".*/([^/]*)(.sim)(.ppd)?(.gz)?$", filename): simplified = "simple" else: simplified = "expert" opt_r = optargs.get ('r') if opt_r: try: opt_r = float (opt_r) except ValueError: opt_r = None url_list = [] if (((opt_r != None and opt_r < 5.2) or (optargs.has_key ('l') and optargs['l'] != "")) and locale != ""): if region: url_list.append ("gutenprint.%s://%s/%s/%s_%s" % version, driver, simplified, locale, region) url_list.append ("gutenprint.%s://%s/%s/%s" % version, driver, simplified, locale) url_list.append ("gutenprint.%s://%s/%s" % (version, driver, simplified)) for url in url_list: new_ppd_filename = url if debug & 8: if server_multicat: cat = "" else: cat = "%s cat " % driver_bin print ("Trying %s%s for %s, %s, %s, %s" % (cat, url, driver, simplified, locale, region)) if server_multicat: try: if not server_multicat_initialized: mc_proc = subprocess.Popen ([driver_bin, "org.gutenprint.multicat"], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=file("/dev/null", "w")) server_multicat_initialized = mc_proc print >>server_multicat_initialized.stdin, "%s" % url server_multicat_initialized.stdin.flush () return (new_ppd_filename, server_multicat_initialized.stdout) except OSError: pass try: proc = subprocess.Popen ([driver_bin, "cat", url], shell=False, stdin=file("/dev/null"), stdout=subprocess.PIPE, stderr=file("/dev/null", "w")) return (new_ppd_filename, proc.stdout) except OSError: pass # Otherwise fall through and try to find a static PPD # Search for a PPD matching our criteria... new_ppd_filename = find_ppd (filename, driver, locale, region) if not new_ppd_filename: # There wasn't a valid source PPD file, so give up. print >>sys.stderr, ("%s: no valid candidate for replacement. " "Skipping" % ppd_source_filename) print >>sys.stderr, ("%s: please upgrade this PPD manually" % ppd_source_filename) return ("", None) if debug & 1: print "Candidate PPD: %s" % new_ppd_filename suffix = "\\" + gzext # Add '\' so the regexp matches the '.' if new_ppd_filename.endswith (".gz"): # Decompress input buffer try: proc = subprocess.Popen (['gunzip', '-c', new_ppd_filename], shell=False, stdin=file("/dev/null"), stdout=subprocess.PIPE, stderr=file("/dev/null", "w")) except OSError, (e, s): print "can't open for decompression: %s" % s sys.exit (1) return (new_ppd_filename, proc.stdout) else: return (new_ppd_filename, file (new_ppd_filename)) def find_ppd (gutenprintfilename, drivername, lang, region): global file_version global optargs global ppd_base_dir global ppd_root_dir global debug key = '^\\*FileVersion:[ ]*' + file_version match = re.search (".*/([^/]+\.[0-9]+\.[0-9]+)(\.sim)?(\.ppd)?(\.gz)?$", gutenprintfilename) if not match: return None stored_name = match.groups ()[0] if re.search (".*/([^/]*)(\.sim)(\.ppd)?(\.gz)?$", gutenprintfilename): simplified = ".sim" else: simplified = "" stored_dir = os.path.dirname (gutenprintfilename) current_best_file = "" current_best_time = 0 if optargs.has_key ('s'): basedirs = [optargs['s']] else: basedirs = [ppd_base_dir, stored_dir, ppd_root_dir] lingos = [] if region != "": lingos.append ("%s_%s/" % (lang, region)) lingos.append ("%s/" % lang) if lang != "C": lingos.append ("C/") lingos.append ("en/") lingos.append ("") lingos.append ("Global/") bases = ["stp-%s.%s%s" % (drivername, version, simplified), "%s.%s%s" % (drivername, version, simplified)] if stored_name not in bases: bases.append (stored_name) bases.append (drivername) # All possible candidates, in order of usefulness and gzippedness for lingo in lingos: for suffix in (".ppd%s" % gzext, ".ppd"): for base in bases: for basedir in basedirs: if basedir == "" or base == "": continue fn = "%s/%s%s%s" % (basedir, lingo, base, suffix) if debug & 8: print ("Trying %s for %s, %s, %s" % (fn, gutenprintfilename, lang, region)) try: st = os.stat (fn) except OSError: continue if (optargs.has_key ('f') or (stat.S_ISREG (st.st_mode) and st.st_uid == 0)): # Check that the file is a valid Gutenprint PPD file # of the correct version. if fn.endswith (".gz"): cmdline = "gunzip -c '%s' | grep '%s'" % (fn, key) else: cmdline = "cat '%s' | grep '%s'" % (fn, key) try: p = subprocess.Popen (cmdline, stdin=file("/dev/null"), stdout=subprocess.PIPE, stderr=file("/dev/null", "w")) except OSError: new_file_version = "" else: (stdin, stderr) = p.communicate () new_file_version = stdin.rstrip () if new_file_version != "": if debug & 8: print (" Format valid: time %s best %s " "prev %s cur %s!" % (st.st_mtime, current_best_time, current_best_file, fn)) if st.st_mtime > current_best_time: current_best_time = st.st_mtime current_best_file = fn if debug & 8: print >>sys.stderr, ("***current_best_file " " is %s" % fn) elif debug & 8: print " Format invalid" else: if (not stat.S_ISDIR (st.st_mode) and not fn.endswith ("/")): print >>sys.stderr, ("%s: not a regular file, " "or insecure ownership and " "permissions. Skipped" % fn) if current_best_file: return current_best_file # Yikes! Cannot find a valid PPD file! return None debug=0 verbose=0 interactive=0 quiet=0 no_action=0 reset_defaults=0 version="@GUTENPRINT_MAJOR_VERSION@.@GUTENPRINT_MINOR_VERSION@" micro_version="@GUTENPRINT_VERSION@" use_static_ppd="@BUILD_CUPS_PPDS@" file_version='"@VERSION@"$' ppd_dir = "@cups_conf_serverroot@/ppd" ppd_root_dir = "@cups_conf_datadir@/model"; ppd_base_dir = ppd_root_dir + "/gutenprint/" + version ppd_out_dir = "" gzext = ".gz" updated_ppd_count = 0 skipped_ppd_count = 0 failed_ppd_count = 0 exit_after_parse_args = 0 languages=["Global", "C"] + "@ALL_LINGUAS@".split (' ') serverdir = "@cups_conf_serverbin@" driver_bin = serverdir + "/driver/gutenprint." + version driver_version = "" server_multicat = 0 server_multicat_initialized = 0 if os.access (driver_bin, os.X_OK): get_driver_version () ppd_files = [] languagemappings = { "chinese": "cn", "danish": "da", "dutch": "nl", "english": "en", "finnish": "fi", "french": "fr", "german": "de", "greek": "el", "hungarian": "hu", "italian": "it", "japanese": "jp", "norwegian": "no", "polish": "pl", "portuguese": "pt", "russian": "ru", "slovak": "sk", "spanish": "es", "swedish": "sv", "turkish": "tr" } # Check command-line options... args = parse_options() # Set a secure umask... os.umask (0177) # Find all in-use Gutenprint PPD files... # For case-insensitive filesystems, use only one of .ppd and .PPD # (bug 1929738) for f in args: if (os.access (f, os.F_OK) and (f.lower ().endswith (".ppd") or f.find ("/") != -1)): ppd_files.append (f) elif os.access ("%s/%s" % (ppd_dir, f), os.F_OK): ppd_files.append ("%s/%s" % (ppd_dir, f)) elif os.access ("%s/%s.ppd" % (ppd_dir, f), os.F_OK): ppd_files.append ("%s/%s.ppd" % (ppd_dir, f)) elif os.access ("%s/%s.PPD" % (ppd_dir, f), os.F_OK): ppd_files.append ("%s/%s.PPD" % (ppd_dir, f)) else: print >>sys.stderr, ("Cannot find file %s/%s, %s/%s.ppd, or %s/%s.PPD" % ppd_dir, f, ppd_dir, f, ppd_dir, f) if len (args) == 0: ppdtmp = glob.glob ("%s/*.ppd" % ppd_dir) ppdtmp += glob.glob ("%s/*.PPD" % ppd_dir) ppd_map = dict() for each in ppdtmp: ppd_map[each] = 1 for f in ppdtmp: if f.endswith (".PPD"): g = f[:-4] + ".ppd" if not ppd_map.has_key (g): ppd_files.append (f) else: ppd_files.append (f) # Update each of the Gutenprint PPDs, where possible... for ppd_file in ppd_files: status = update_ppd (ppd_file) if status == -2: break elif status == 0: failed_ppd_count += 1 elif status == 1: updated_ppd_count += 1 elif status == -1: skipped_ppd_count += 1 if (not quiet) or verbose: if len (ppd_files) == 0: print "No Gutenprint PPD files to update." elif updated_ppd_count > 0: plural = "" if updated_ppd_count != 1: plural = "s" print "Updated %d PPD file%s" % (updated_ppd_count, plural) if ((not optargs.has_key ('o')) or optargs['o'] != ""): print "Restart cupsd for the changes to take effect." else: if failed_ppd_count > 0: print "Failed to update any PPD files" else: print "Did not update any PPD files" sys.exit (failed_ppd_count > 0)