#!/usr/bin/env python # -*- coding: utf-8 -*- # # (c) Copyright 2003-2008 Hewlett-Packard Development Company, L.P. # # 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 of the License, 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 # # Author: Don Welch, Naga Samrat Chowdary Narla, # Local from base.g import * from base import utils # StdLib import os.path import re import glob try: import datetime datetime_avail = True except ImportError: datetime_avail = False datetime = None pat_prod_num = re.compile("""(\d+)""", re.I) TYPE_UNKNOWN = 0 TYPE_STRING = 1 TYPE_STR = 1 TYPE_LIST = 2 TYPE_BOOL = 3 TYPE_INT = 4 TYPE_HEX = 5 TYPE_BITFIELD = 6 TYPE_URI = TYPE_STR # (7) not used (yet) TYPE_DATE = 8 # format: mm/dd/yyyy TECH_CLASSES = [ "Undefined", # This will show an error (and its the default) "Unsupported", # This is for unsupported models, and it will not show an error "Postscript", "DJGenericVIP", #"PSB9100", not used on HPLIP "LJMono", "LJColor", "LJFastRaster", "LJJetReady", "DJ350", #"DJ400", not used on HPLIP "DJ540", "DJ600", "DJ6xx", "DJ6xxPhoto", "DJ630", #"DJ660", not used in HPLIP "DJ8xx", "DJ8x5", "DJ850", "DJ890", "DJ9xx", "DJ9xxVIP", "DJ3600", "DJ3320", "DJ4100", "AP2xxx", "AP21xx", "AP2560", "PSP100", "PSP470", "LJZjsMono", "LJZjsColor", "LJm1005", "QuickConnect", "DJ55xx", "OJProKx50", 'LJP1XXX', #'DJD2600', not used. Reassigned all these to ViperPlusTrim and ViperMinusTrim Class "Stabler", "ViperPlusVIP", "ViperMinusVIP", "ViperPlusTrim", "ViperMinusTrim", "Corbett", "Python", "OJ7000", "Pyramid", "Python10", "Mimas", "StingrayOJ", "Copperhead", "Ampere", "Python11", ] TECH_CLASSES.sort() TECH_CLASS_PDLS = { #"Undefined" : '?', "Postscript" : 'ps', "DJGenericVIP" : 'pcl3', #"PSB9100" : 'pcl3', "LJMono" : 'pcl3', "LJColor" : 'pcl3', "LJFastRaster" : 'pclxl', "LJJetReady" : 'pclxl', "DJ350" : 'pcl3', #"DJ400" : 'pcl3', "DJ540" : 'pcl3', "DJ600" : 'pcl3', "DJ6xx" : 'pcl3', "DJ6xxPhoto" : 'pcl3', "DJ630" : 'pcl3', #"DJ660" : 'pcl3', "DJ8xx" : 'pcl3', "DJ8x5" : 'pcl3', "DJ850" : 'pcl3', "DJ890" : 'pcl3', "DJ9xx" : 'pcl3', "DJ9xxVIP" : 'pcl3', "DJ3600" : 'lidil', "DJ3320" : 'lidil', "DJ4100" : 'lidil', "AP2xxx" : 'pcl3', "AP21xx" : 'pcl3', "AP2560" : 'pcl3', "PSP100" : 'pcl3', "PSP470" : 'pcl3', "LJZjsMono" : 'zjs', "LJZjsColor" : 'zjs', "LJm1005" : 'zxs', "QuickConnect" : 'jpeg', "DJ55xx" : 'pcl3', "OJProKx50" : 'pcl3', 'LJP1XXX' : 'zxs', "Stabler" : 'pcl3', "ViperPlusVIP" : 'pcl3', "ViperMinusVIP": 'pcl3', "ViperPlusTrim" : 'lidil', "ViperMinusTrim": 'lidil', "Corbett" : 'pcl3', "Python" : 'pcl3', "OJ7000" : 'pcl3', "Python10" : 'pcl3', "Mimas" : 'pcl3', "StingrayOJ" : 'pcl3', "Copperhead" : 'pcl3', "Ampere" : 'pcl3' } PDL_TYPE_PCL = 0 # less preferred PDL_TYPE_PS = 1 # /\ PDL_TYPE_HOST = 2 # more preferred (however, may req. plugin) PDL_TYPES = { # Used to prioritize PPD file selection in prnt.cups.getPPDFile2() 'pcl3' : PDL_TYPE_PCL, 'pcl5' : PDL_TYPE_PCL, 'pcl6' : PDL_TYPE_PCL, 'pcl5e' : PDL_TYPE_PCL, 'pcl' : PDL_TYPE_PCL, 'pclxl' : PDL_TYPE_PCL, 'ps' : PDL_TYPE_PS, 'lidil' : PDL_TYPE_HOST, 'zjs' : PDL_TYPE_HOST, 'zjstream' : PDL_TYPE_HOST, 'zxs' : PDL_TYPE_HOST, 'zxstream' : PDL_TYPE_HOST, 'jpeg' : PDL_TYPE_HOST, 'jpg' : PDL_TYPE_HOST, 'jetready' : PDL_TYPE_HOST, 'jr' : PDL_TYPE_HOST, } TECH_SUBCLASSES = [ "LargeFormatSuperB", "LargeFormatA3", "CoverMedia", # 3425 "FullBleed", "Duplex", "Normal", "Apollo2000", "Apollo2200", "Apollo2500", "NoPhotoMode", "NoPhotoBestHiresModes", "No1200dpiNoSensor", "NoFullBleed", "4x6FullBleed", "300dpiOnly", # LaserJet 4L "GrayscaleOnly", # DJ540 "NoAutoTray", # PS Pro 8850 "NoEvenDuplex", # PS C8100 "NoAutoDuplex", "NoCDDVD", "NoMaxDPI", "NoMaxDPI", "SmallMargins", "Trim", "4800x1200dpi", "Advanced", "AutoDuplex" ] TECH_SUBCLASSES.sort() # Items will be capitalized unless in this dict MODEL_UI_REPLACEMENTS = {'laserjet' : 'LaserJet', 'psc' : 'PSC', 'hp' : 'HP', 'mfp' : 'MFP', } def normalizeModelUIName(model): ml = model.lower().strip() if 'apollo' in ml: z = ml.replace('_', ' ') else: if ml.startswith("hp"): z = ml[3:].replace('_', ' ') else: z = ml.replace('_', ' ') y = [] for x in z.split(): if pat_prod_num.search(x): # don't cap items like cp1700dn y.append(x) else: y.append(MODEL_UI_REPLACEMENTS.get(x, x.capitalize())) if 'apollo' in ml: return ' '.join(y) else: return "HP " + ' '.join(y) def normalizeModelName(model): return utils.xstrip(model.replace(' ', '_').replace('__', '_').replace('~','').replace('/', '_'), '_') class ModelData: def __init__(self, root_path=None): if root_path is None: self.root_path = prop.models_dir else: self.root_path = root_path self.__cache = {} self.reset_includes() self.sec = re.compile(r'^\[(.*)\]') self.inc = re.compile(r'^\%include (.*)', re.I) self.inc_line = re.compile(r'^\%(.*)\%') self.eq = re.compile(r'^([^=]+)=(.*)') self.date = re.compile(r'^(\d{1,2})/(\d{1,2})/(\d{4,4})') files = [(os.path.join(self.root_path, "models.dat"), os.path.join(self.root_path, "unreleased", "unreleased.dat")), (os.path.join(os.getcwd(), 'data', 'models', 'models.dat'), os.path.join(os.getcwd(), 'data', 'models', 'unreleased', 'unreleased.dat'))] for self.released_dat, self.unreleased_dat in files: if os.path.exists(self.released_dat): break else: self.released_dat, self.unreleased_dat = None, None log.error("Unable to locate models.dat file") self.FIELD_TYPES = { # Static model query data (from models.dat) 'align-type' : TYPE_INT, 'clean-type' : TYPE_INT, 'color-cal-type' : TYPE_INT, 'copy-type' : TYPE_INT, 'embedded-server-type' : TYPE_INT, 'fax-type' : TYPE_INT, 'fw-download' : TYPE_BOOL, 'icon' : TYPE_STR, 'io-mfp-mode' : TYPE_INT, 'io-mode' : TYPE_INT, 'io-support' : TYPE_BITFIELD, 'job-storage' : TYPE_INT, 'monitor-type' : TYPE_INT, 'linefeed-cal-type' : TYPE_INT, 'panel-check-type' : TYPE_INT, 'pcard-type' : TYPE_INT, 'plugin' : TYPE_INT, 'plugin-reason' : TYPE_BITFIELD, 'power-settings': TYPE_INT, 'pq-diag-type' : TYPE_INT, 'r-type' : TYPE_INT, 'scan-style' : TYPE_INT, 'scan-type' : TYPE_INT, 'scan-src' : TYPE_INT, #'scan-duplex' : TYPE_BOOL, 'status-battery-check' : TYPE_INT, 'status-dynamic-counters' : TYPE_INT, 'status-type' : TYPE_INT, 'support-subtype' : TYPE_HEX, 'support-released' : TYPE_BOOL, 'support-type' : TYPE_INT, 'support-ver' : TYPE_STR, 'tech-class' : TYPE_LIST, 'tech-subclass' : TYPE_LIST, 'tech-type' : TYPE_INT, 'usb-pid' : TYPE_HEX, 'usb-vid' : TYPE_HEX, 'wifi-config': TYPE_INT, 'ppd-name' : TYPE_STR, } self.FIELD_TYPES_DYN = { # Dynamic model data (from device query) 'dev-file' : TYPE_STR, 'fax-uri' : TYPE_STR, 'scan-uri' : TYPE_STR, 'is-hp' : TYPE_BOOL, 'host' : TYPE_STR, 'status-desc' : TYPE_STR, 'cups-printers' : TYPE_STR, 'serial' : TYPE_STR, 'error-state' : TYPE_INT, 'device-state' : TYPE_INT, 'panel' : TYPE_INT, 'device-uri' : TYPE_STR, 'panel-line1' : TYPE_STR, 'panel-line2' : TYPE_STR, 'back-end' : TYPE_STR, 'port' : TYPE_INT, 'deviceid' : TYPE_STR, 'cups-uri' : TYPE_STR, 'status-code' : TYPE_INT, 'rs' : TYPE_STR, 'rr' : TYPE_STR, 'rg' : TYPE_STR, 'r' : TYPE_INT, 'duplexer' : TYPE_INT, 'supply-door' : TYPE_INT, 'revision' : TYPE_INT, 'media-path' : TYPE_INT, 'top-door' : TYPE_BOOL, 'photo-tray' : TYPE_BOOL, } self.RE_FIELD_TYPES = { re.compile('^r(\d+)-agent(\d+)-kind', re.IGNORECASE) : TYPE_INT, re.compile('^r(\d+)-agent(\d+)-type', re.IGNORECASE) : TYPE_INT, re.compile('^r(\d+)-agent(\d+)-sku', re.IGNORECASE) : TYPE_STR, re.compile('^agent(\d+)-desc', re.IGNORECASE) : TYPE_STR, re.compile('^agent(\d+)-virgin', re.IGNORECASE) : TYPE_BOOL, re.compile('^agent(\d+)-dvc', re.IGNORECASE) : TYPE_INT, re.compile('^agent(\d+)-kind', re.IGNORECASE) : TYPE_INT, re.compile('^agent(\d+)-type', re.IGNORECASE) : TYPE_INT, re.compile('^agent(\d+)-id', re.IGNORECASE) : TYPE_INT, re.compile('^agent(\d+)-hp-ink', re.IGNORECASE) : TYPE_BOOL, re.compile('^agent(\d+)-health-desc', re.IGNORECASE) : TYPE_STR, re.compile('^agent(\d+)-health$', re.IGNORECASE) : TYPE_INT, re.compile('^agent(\d+)-known', re.IGNORECASE) : TYPE_BOOL, re.compile('^agent(\d+)-level', re.IGNORECASE) : TYPE_INT, re.compile('^agent(\d+)-ack', re.IGNORECASE) : TYPE_BOOL, re.compile('^agent(\d+)-sku', re.IGNORECASE) : TYPE_STR, re.compile('^in-tray(\d+)', re.IGNORECASE) : TYPE_BOOL, re.compile('^out-tray(\d+)', re.IGNORECASE) : TYPE_BOOL, re.compile('^model(\d+)', re.IGNORECASE) : TYPE_STR, } self.TYPE_CACHE = {} def read_all_files(self, unreleased=True): if os.path.exists(self.released_dat): self.read_section(self.released_dat) if self.unreleased_dat is not None and os.path.exists(self.unreleased_dat): self.read_section(self.unreleased_dat ) return self.__cache def read_section(self, filename, section=None, is_include=False): # section==None, read all sections found, in_section = False, False if section is not None: section = section.lower() if is_include: log.debug("Searching for include [%s] in file %s" % (section, filename)) else: log.debug("Searching for section [%s] in file %s" % (section, filename)) if is_include: cache = self.__includes else: cache = self.__cache try: fd = file(filename) except IOError, e: log.error("I/O Error: %s (%s)" % (filename, e.strerror)) return False while True: line = fd.readline() if not line: break if line[0] in ('#', ';'): continue if line[0] == '[': if in_section and section is not None: break match = self.sec.search(line) if match is not None: in_section = True read_section = match.group(1).lower() if section is not None: found = in_section = (read_section == section) if in_section: if section is not None: log.debug("Found section [%s] in file %s" % (read_section, filename)) cache[read_section] = {} continue if line[0] == '%': match = self.inc.match(line) if match is not None: inc_file = match.group(1) log.debug("Found include file directive: %%include %s" % inc_file) self.__include_files.append(os.path.join(os.path.dirname(filename), inc_file)) continue if in_section: match = self.inc_line.match(line) if match is not None: inc_sect = match.group(1) log.debug("Found include directive %%%s%%" % inc_sect) try: self.__includes[inc_sect] except KeyError: for inc in self.__include_files: if self.read_section(inc, inc_sect, True): break else: log.error("Include %%%s%% not found." % inc_sect) if in_section: match = self.eq.search(line) if match is not None: key = match.group(1) value = match.group(2) value = self.convert_data(key, value) cache[read_section][key] = value fd.close() return found def reset_includes(self): self.__include_files = [] self.__includes = {} def __getitem__(self, model): model = model.lower() try: return self.__cache[model] except: log.debug("Cache miss: %s" % model) log.debug("Reading file: %s" % self.released_dat) if self.read_section(self.released_dat, model): return self.__cache[model] if self.unreleased_dat is not None and os.path.exists(self.unreleased_dat): log.debug("Reading file: %s" % self.unreleased_dat) if self.read_section(self.unreleased_dat, model): return self.__cache[model] return {} def all_models(self): return self.__cache def get_data_type(self, key): try: return self.FIELD_TYPES[key] except KeyError: try: return self.FIELD_TYPES_DYN[key] except KeyError: try: return self.TYPE_CACHE[key] except KeyError: for pat, typ in self.RE_FIELD_TYPES.items(): match = pat.match(key) if match is not None: self.TYPE_CACHE[key] = typ return typ log.error("get_data_type(): Field type lookup failed for key %s" % key) return None def convert_data(self, key, value, typ=None): if typ is None: typ = self.get_data_type(key) if typ in (TYPE_BITFIELD, TYPE_INT): try: value = int(value) except (ValueError, TypeError): log.error("Invalid value in .dat file: %s=%s" % (key, value)) value = 0 elif typ == TYPE_BOOL: value = utils.to_bool(value) elif typ == TYPE_LIST: value = [x for x in value.split(',') if x] elif typ == TYPE_DATE: # mm/dd/yyyy if datetime_avail: # ...don't use datetime.strptime(), wasn't avail. until 2.5 match = self.date.search(value) if match is not None: month = int(match.group(1)) day = int(match.group(2)) year = int(match.group(3)) value = datetime.date(year, month, day) elif typ == TYPE_HEX: try: value = int(value, 16) except (ValueError, TypeError): log.error("Invalid hex value in .dat file: %s=%s" % (key, value)) value = 0 return value