#!/usr/bin/python3 # $Id$ # Update CUPS PPDs for Gutenprint queues. # Copyright (C) 2002-2003 Roger Leigh (rleigh@debian.org) # Copyright (C) 2009, 2011, 2014 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 io import os import re import stat import subprocess import sys from functools import reduce 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 as err: (e, s) = err.args 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=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=False) (stdout, stderr) = p.communicate () except OSError: return None if stdout != None: stdout = stdout.decode () 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 'n' in optargs: no_action = 1 if 'd' in optargs: try: debug = int (optargs['d']) except ValueError: d = 0 if 'v' in optargs: verbose = 1 quiet = 0 if 'q' in optargs: verbose = 0 quiet = 1 if 'N' in optargs: reset_defaults = 1 if 'o' in optargs: opt_o = optargs['o'] die_if_not_directory (opt_o) ppd_out_dir = opt_o if 'r' in optargs: opt_r = optargs['r'] if version != opt_r: version = opt_r if 's' in optargs: 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 's' in optargs: 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 'p' in optargs: opt_p = optargs['p'] die_if_not_directory (opt_p) ppd_dir = opt_p if 'P' in optargs: 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 'h' in optargs: help () if ('l' in optargs and optargs['l'].lower () != "original" and optargs['l'].lower () not in languages): print ("Unknown language '%s'" % optargs['l'], file=sys.stderr) if 'i' in optargs: 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 = open (ppd_source_filename, encoding="utf-8") 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 (("Skipping %s: not a Gutenprint PPD file" % # ppd_source_filename), file=sys.stderr) return -1 if ('l' in optargs and optargs['l'] != "" and optargs['l'].lower () != "original"): locale = optargs['l'] orig_locale = locale if debug & 2: print ("Gutenprint Filename: %s" % filename) if 'l' in optargs: 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 = input ("Update PPD %s from %s [nyq]? " % ppd_source_filename) inp = inp.lower () if inp.startswith ("q"): if server_multicat: source_fd.detach () else: source_fd.close () print ("Skipping all...") return -2 elif not inp.startswith ("y"): if server_multicat: source_fd.detach () else: 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 server_multicat: source_fd.detach () else: 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.items (): 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 = list(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 (list(resolution_map.keys ())) > 0: print ("Resolution Map:") keys = list(resolution_map.keys ()) keys.sort () for key in keys: print (" %s: %s" % (key, resolution_map[key])) if len (list(old_resolution_map.keys ())) > 0: print ("Old Resolution Map:") keys = list(old_resolution_map.keys ()) keys.sort () for key in keys: print (" %s: %s" % (key, old_resolution_map[key])) print ("Non-UI Defaults:") keys = list(defaults.keys ()) keys.sort () for key in keys: xkey = key if xkey.startswith ("Default"): xkey = xkey[7:] if xkey not in options: print (" %s: %s" % (key, defaults[key])) print ("Default Types of dropped options:") keys = list(orig_default_types.keys ()) keys.sort () for t in keys: if t not in options: 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 = list(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 def_option in old_resolution_map): 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 dopt in resolution_map): 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 = open (tmpnew, "w") except IOError as err: (e, s) = err.args print ("Can't create %s: %s" % (tmpnew, s)) return 0 newppd.writelines (source_data) try: newppd.close () except IOError as err: (e, s) = err.args print ("Can't write to %s: %s" % (tmpnew, s)) return 0 chcon = subprocess.Popen (["chcon", "--reference=%s" % ppd_dest_filename, tmpnew], shell=False, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) chcon.communicate () try: os.rename (tmpnew, ppd_dest_filename) except OSError as err: (e, s) = err.args 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 & 0o777) 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 ('l' in optargs 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=subprocess.DEVNULL) server_multicat_initialized = mc_proc mc_in = io.TextIOWrapper (server_multicat_initialized.stdin) print ("%s" % url, file=mc_in) mc_in.flush () mc_in.detach () return (new_ppd_filename, io.TextIOWrapper (server_multicat_initialized.stdout)) except OSError: pass try: proc = subprocess.Popen ([driver_bin, "cat", url], shell=False, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) return (new_ppd_filename, io.TextIOWrapper (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 (("%s: no valid candidate for replacement. Skipping" % ppd_source_filename), file=sys.stderr) print (("%s: please upgrade this PPD manually" % ppd_source_filename), file=sys.stderr) 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=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) except OSError as err: (e, s) = err.args print ("can't open for decompression: %s" % s) sys.exit (1) return (new_ppd_filename, io.TextIOWrapper (proc.stdout)) else: return (new_ppd_filename, open (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 's' in optargs: 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 ('f' in optargs 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=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) except OSError: new_file_version = "" else: (stdin, stderr) = p.communicate () new_file_version = stdin.decode ().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 (("***current_best_file " " is %s" % fn), file=sys.stderr) elif debug & 8: print (" Format invalid") else: if (not stat.S_ISDIR (st.st_mode) and not fn.endswith ("/")): print (("%s: not a regular file, " "or insecure ownership and " "permissions. Skipped" % fn), file=sys.stderr) 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 (0o177) # 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 (("Cannot find file %s/%s, %s/%s.ppd, or %s/%s.PPD" % ppd_dir, f, ppd_dir, f, ppd_dir, f), file=sys.stderr) 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 g not in ppd_map: 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 (('o' not in optargs) 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)