Tizen 2.1 base
[platform/upstream/hplip.git] / base / models.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2008 Hewlett-Packard Development Company, L.P.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 #
20 # Author: Don Welch, Naga Samrat Chowdary Narla,
21
22 # Local
23 from base.g import *
24 from base import utils
25
26 # StdLib
27 import os.path
28 import re
29 import glob
30
31 try:
32     import datetime
33     datetime_avail = True
34 except ImportError:
35     datetime_avail = False
36     datetime = None
37
38
39 pat_prod_num = re.compile("""(\d+)""", re.I)
40
41 TYPE_UNKNOWN = 0
42 TYPE_STRING = 1
43 TYPE_STR = 1
44 TYPE_LIST = 2
45 TYPE_BOOL = 3
46 TYPE_INT = 4
47 TYPE_HEX = 5
48 TYPE_BITFIELD = 6
49 TYPE_URI = TYPE_STR # (7) not used (yet)
50 TYPE_DATE = 8  # format: mm/dd/yyyy
51
52
53 TECH_CLASSES = [
54     "Undefined", # This will show an error (and its the default)
55     "Unsupported", # This is for unsupported models, and it will not show an error
56     "Postscript",
57     "DJGenericVIP",
58     #"PSB9100", not used on HPLIP
59     "LJMono",
60     "LJColor",
61     "LJFastRaster",
62     "LJJetReady",
63     "DJ350",
64     #"DJ400", not used on HPLIP
65     "DJ540",
66     "DJ600",
67     "DJ6xx",
68     "DJ6xxPhoto",
69     "DJ630",
70     #"DJ660", not used in HPLIP
71     "DJ8xx",
72     "DJ8x5",
73     "DJ850",
74     "DJ890",
75     "DJ9xx",
76     "DJ9xxVIP",
77     "DJ3600",
78     "DJ3320",
79     "DJ4100",
80     "AP2xxx",
81     "AP21xx",
82     "AP2560",
83     "PSP100",
84     "PSP470",
85     "LJZjsMono",
86     "LJZjsColor",
87     "LJm1005",
88     "QuickConnect",
89     "DJ55xx",
90     "OJProKx50",
91     'LJP1XXX',
92     #'DJD2600', not used. Reassigned all these to ViperPlusTrim and ViperMinusTrim Class
93     "Stabler",
94     "ViperPlusVIP",
95     "ViperMinusVIP",
96     "ViperPlusTrim",
97     "ViperMinusTrim",
98     "Corbett",
99     "Python",
100     "OJ7000",
101     "Pyramid",
102     "Python10",
103     "Mimas",
104     "StingrayOJ",
105     "Copperhead",
106     "Ampere",
107     "Python11",
108     "Saipan",
109 ]
110
111 TECH_CLASSES.sort()
112
113 TECH_CLASS_PDLS = {
114     #"Undefined"    : '?',
115     "Postscript"   : 'ps',
116     "DJGenericVIP" : 'pcl3',
117     #"PSB9100"      : 'pcl3',
118     "LJMono"       : 'pcl3',
119     "LJColor"      : 'pcl3',
120     "LJFastRaster" : 'pclxl',
121     "LJJetReady"   : 'pclxl',
122     "DJ350"        : 'pcl3',
123     #"DJ400"        : 'pcl3',
124     "DJ540"        : 'pcl3',
125     "DJ600"        : 'pcl3',
126     "DJ6xx"        : 'pcl3',
127     "DJ6xxPhoto"   : 'pcl3',
128     "DJ630"        : 'pcl3',
129     #"DJ660"        : 'pcl3',
130     "DJ8xx"        : 'pcl3',
131     "DJ8x5"        : 'pcl3',
132     "DJ850"        : 'pcl3',
133     "DJ890"        : 'pcl3',
134     "DJ9xx"        : 'pcl3',
135     "DJ9xxVIP"     : 'pcl3',
136     "DJ3600"       : 'lidil',
137     "DJ3320"       : 'lidil',
138     "DJ4100"       : 'lidil',
139     "AP2xxx"       : 'pcl3',
140     "AP21xx"       : 'pcl3',
141     "AP2560"       : 'pcl3',
142     "PSP100"       : 'pcl3',
143     "PSP470"       : 'pcl3',
144     "LJZjsMono"    : 'zjs',
145     "LJZjsColor"   : 'zjs',
146     "LJm1005"      : 'zxs',
147     "QuickConnect" : 'jpeg',
148     "DJ55xx"       : 'pcl3',
149     "OJProKx50"    : 'pcl3',
150     'LJP1XXX'      : 'zxs',
151     "Stabler"      : 'pcl3',
152     "ViperPlusVIP" : 'pcl3',
153     "ViperMinusVIP": 'pcl3',
154     "ViperPlusTrim" : 'lidil',
155     "ViperMinusTrim": 'lidil',
156     "Corbett"       : 'pcl3',
157     "Python"        : 'pcl3',
158     "OJ7000"        : 'pcl3',
159     "Python10"      : 'pcl3',
160     "Mimas"      : 'pcl3',
161     "StingrayOJ"   : 'pcl3',
162     "Copperhead"   : 'pcl3',
163     "Copperhead12"   : 'pcl3',
164     "Ampere"       : 'pcl3'
165 }
166
167 PDL_TYPE_PCL = 0  # less preferred
168 PDL_TYPE_PS = 1   #      /\
169 PDL_TYPE_HOST = 2 # more preferred (however, may req. plugin)
170
171 PDL_TYPES = { # Used to prioritize PPD file selection in prnt.cups.getPPDFile2()
172     'pcl3' : PDL_TYPE_PCL,
173     'pcl5' : PDL_TYPE_PCL,
174     'pcl6' : PDL_TYPE_PCL,
175     'pcl5e' : PDL_TYPE_PCL,
176     'pcl' : PDL_TYPE_PCL,
177     'pclxl' : PDL_TYPE_PCL,
178     'ps' : PDL_TYPE_PS,
179     'lidil' : PDL_TYPE_HOST,
180     'zjs' : PDL_TYPE_HOST,
181     'zjstream' : PDL_TYPE_HOST,
182     'zxs' : PDL_TYPE_HOST,
183     'zxstream' : PDL_TYPE_HOST,
184     'jpeg' : PDL_TYPE_HOST,
185     'jpg' : PDL_TYPE_HOST,
186     'jetready' : PDL_TYPE_HOST,
187     'jr' : PDL_TYPE_HOST,
188 }
189
190
191 TECH_SUBCLASSES = [
192     "LargeFormatSuperB",
193     "LargeFormatA3",
194     "CoverMedia", # 3425
195     "FullBleed",
196     "Duplex",
197     "Normal",
198     "Apollo2000",
199     "Apollo2200",
200     "Apollo2500",
201     "NoPhotoMode",
202     "NoPhotoBestHiresModes",
203     "No1200dpiNoSensor",
204     "NoFullBleed",
205     "4x6FullBleed",
206     "300dpiOnly",  # LaserJet 4L
207     "GrayscaleOnly", # DJ540
208     "NoAutoTray", # PS Pro 8850
209     "NoEvenDuplex", # PS C8100
210     "NoAutoDuplex",
211     "NoCDDVD",
212     "NoMaxDPI",
213     "NoMaxDPI",
214     "SmallMargins",
215     "Trim",
216     "4800x1200dpi",
217     "Advanced",
218     "AutoDuplex"
219 ]
220
221 TECH_SUBCLASSES.sort()
222
223
224 # Items will be capitalized unless in this dict
225 MODEL_UI_REPLACEMENTS = {'laserjet'   : 'LaserJet',
226                           'psc'        : 'PSC',
227                           'hp'         : 'HP',
228                           'mfp'        : 'MFP',
229                         }
230
231
232 def normalizeModelUIName(model):
233     ml = model.lower().strip()
234
235     if 'apollo' in ml:
236         z = ml.replace('_', ' ')
237     else:
238         if ml.startswith("hp"):
239             z = ml[3:].replace('_', ' ')
240         else:
241             z = ml.replace('_', ' ')
242
243     y = []
244     for x in z.split():
245         if pat_prod_num.search(x): # don't cap items like cp1700dn
246             y.append(x)
247         else:
248             y.append(MODEL_UI_REPLACEMENTS.get(x, x.capitalize()))
249
250     if 'apollo' in ml:
251         return ' '.join(y)
252     else:
253         return "HP " + ' '.join(y)
254
255
256 def normalizeModelName(model):
257     return utils.xstrip(model.replace(' ', '_').replace('__', '_').replace('~','').replace('/', '_'), '_')
258
259
260 class ModelData:
261     def __init__(self, root_path=None):
262         if root_path is None:
263             self.root_path = prop.models_dir
264         else:
265             self.root_path = root_path
266
267         self.__cache = {}
268         self.reset_includes()
269         self.sec = re.compile(r'^\[(.*)\]')
270         self.inc = re.compile(r'^\%include (.*)', re.I)
271         self.inc_line = re.compile(r'^\%(.*)\%')
272         self.eq = re.compile(r'^([^=]+)=(.*)')
273         self.date = re.compile(r'^(\d{1,2})/(\d{1,2})/(\d{4,4})')
274
275         files = [(os.path.join(self.root_path, "models.dat"),
276                   os.path.join(self.root_path, "unreleased", "unreleased.dat")),
277                  (os.path.join(os.getcwd(), 'data', 'models', 'models.dat'),
278                   os.path.join(os.getcwd(), 'data', 'models', 'unreleased', 'unreleased.dat'))]
279
280         for self.released_dat, self.unreleased_dat in files:
281             if os.path.exists(self.released_dat):
282                 break
283
284         else:
285             self.released_dat, self.unreleased_dat = None, None
286             log.error("Unable to locate models.dat file")
287
288         self.FIELD_TYPES = {
289             # Static model query data (from models.dat)
290             'align-type' : TYPE_INT,
291             'clean-type' : TYPE_INT,
292             'color-cal-type' : TYPE_INT,
293             'copy-type' : TYPE_INT,
294             'embedded-server-type' : TYPE_INT,
295             'fax-type' : TYPE_INT,
296             'fw-download' : TYPE_BOOL,
297             'icon' : TYPE_STR,
298             'io-mfp-mode' : TYPE_INT,
299             'io-mode' : TYPE_INT,
300             'io-support' : TYPE_BITFIELD,
301             'job-storage' : TYPE_INT,
302             'monitor-type' : TYPE_INT,
303             'linefeed-cal-type' : TYPE_INT,
304             'panel-check-type' : TYPE_INT,
305             'pcard-type' : TYPE_INT,
306             'plugin' : TYPE_INT,
307             'plugin-reason' : TYPE_BITFIELD,
308             'power-settings': TYPE_INT,
309             'pq-diag-type' : TYPE_INT,
310             'r-type' : TYPE_INT,
311             'scan-type' : TYPE_INT,
312             'scan-src' : TYPE_INT,
313             #'scan-duplex' : TYPE_BOOL,
314             'status-battery-check' : TYPE_INT,
315             'status-dynamic-counters' : TYPE_INT,
316             'status-type' : TYPE_INT,
317             'support-subtype' : TYPE_HEX,
318             'support-released' : TYPE_BOOL,
319             'support-type' : TYPE_INT,
320             'support-ver' : TYPE_STR,
321             'tech-class' : TYPE_LIST,
322             'tech-subclass' : TYPE_LIST,
323             'tech-type' : TYPE_INT,
324             'usb-pid' : TYPE_HEX,
325             'usb-vid' : TYPE_HEX,
326             'wifi-config': TYPE_INT,
327             'ppd-name' : TYPE_STR,
328             }
329
330         self.FIELD_TYPES_DYN = {
331             # Dynamic model data (from device query)
332             'dev-file' : TYPE_STR,
333             'fax-uri' : TYPE_STR,
334             'scan-uri' : TYPE_STR,
335             'is-hp' : TYPE_BOOL,
336             'host' : TYPE_STR,
337             'status-desc' : TYPE_STR,
338             'cups-printers' : TYPE_STR,
339             'serial' : TYPE_STR,
340             'error-state' : TYPE_INT,
341             'device-state' : TYPE_INT,
342             'panel' : TYPE_INT,
343             'device-uri' : TYPE_STR,
344             'panel-line1' : TYPE_STR,
345             'panel-line2' : TYPE_STR,
346             'back-end' : TYPE_STR,
347             'port' : TYPE_INT,
348             'deviceid' : TYPE_STR,
349             'cups-uri' : TYPE_STR,
350             'status-code' : TYPE_INT,
351             'rs' : TYPE_STR,
352             'rr' : TYPE_STR,
353             'rg' : TYPE_STR,
354             'r' : TYPE_INT,
355             'duplexer' : TYPE_INT,
356             'supply-door' : TYPE_INT,
357             'revision' : TYPE_INT,
358             'media-path' : TYPE_INT,
359             'top-door' : TYPE_BOOL,
360             'photo-tray' : TYPE_BOOL,
361             }
362
363         self.RE_FIELD_TYPES = {
364             re.compile('^r(\d+)-agent(\d+)-kind', re.IGNORECASE) : TYPE_INT,
365             re.compile('^r(\d+)-agent(\d+)-type', re.IGNORECASE) : TYPE_INT,
366             re.compile('^r(\d+)-agent(\d+)-sku', re.IGNORECASE) : TYPE_STR,
367             re.compile('^agent(\d+)-desc', re.IGNORECASE) : TYPE_STR,
368             re.compile('^agent(\d+)-virgin', re.IGNORECASE) : TYPE_BOOL,
369             re.compile('^agent(\d+)-dvc', re.IGNORECASE) : TYPE_INT,
370             re.compile('^agent(\d+)-kind', re.IGNORECASE) : TYPE_INT,
371             re.compile('^agent(\d+)-type', re.IGNORECASE) : TYPE_INT,
372             re.compile('^agent(\d+)-id', re.IGNORECASE) : TYPE_INT,
373             re.compile('^agent(\d+)-hp-ink', re.IGNORECASE) : TYPE_BOOL,
374             re.compile('^agent(\d+)-health-desc', re.IGNORECASE) : TYPE_STR,
375             re.compile('^agent(\d+)-health$', re.IGNORECASE) : TYPE_INT,
376             re.compile('^agent(\d+)-known', re.IGNORECASE) : TYPE_BOOL,
377             re.compile('^agent(\d+)-level', re.IGNORECASE) : TYPE_INT,
378             re.compile('^agent(\d+)-ack', re.IGNORECASE) : TYPE_BOOL,
379             re.compile('^agent(\d+)-sku', re.IGNORECASE) : TYPE_STR,
380             re.compile('^in-tray(\d+)', re.IGNORECASE) : TYPE_BOOL,
381             re.compile('^out-tray(\d+)', re.IGNORECASE) : TYPE_BOOL,
382             re.compile('^model(\d+)', re.IGNORECASE) : TYPE_STR,
383             }
384
385         self.TYPE_CACHE = {}
386
387
388     def read_all_files(self, unreleased=True):
389         if os.path.exists(self.released_dat):
390             self.read_section(self.released_dat)
391
392             if self.unreleased_dat is not None and os.path.exists(self.unreleased_dat):
393                 self.read_section(self.unreleased_dat )
394
395         return self.__cache
396
397
398     def read_section(self, filename, section=None, is_include=False): # section==None, read all sections
399         found, in_section = False, False
400
401         if section is not None:
402             section = section.lower()
403
404             if is_include:
405                 log.debug("Searching for include [%s] in file %s" % (section, filename))
406             else:
407                 log.debug("Searching for section [%s] in file %s" % (section, filename))
408
409         if is_include:
410             cache = self.__includes
411         else:
412             cache = self.__cache
413
414         try:
415             fd = file(filename)
416         except IOError, e:
417             log.error("I/O Error: %s (%s)" % (filename, e.strerror))
418             return False
419
420         while True:
421             line = fd.readline()
422
423             if not line:
424                 break
425
426             if line[0] in ('#', ';'):
427                 continue
428
429             if line[0] == '[':
430                 if in_section and section is not None:
431                     break
432
433                 match = self.sec.search(line)
434
435                 if match is not None:
436                     in_section = True
437
438                     read_section = match.group(1).lower()
439
440                     if section is not None:
441                         found = in_section = (read_section == section)
442
443                     if in_section:
444                         if section is not None:
445                             log.debug("Found section [%s] in file %s" % (read_section, filename))
446
447                         cache[read_section] = {}
448
449                 continue
450
451             if line[0] == '%':
452                 match = self.inc.match(line)
453
454                 if match is not None:
455                     inc_file = match.group(1)
456                     log.debug("Found include file directive: %%include %s" % inc_file)
457                     self.__include_files.append(os.path.join(os.path.dirname(filename), inc_file))
458                     continue
459
460                 if in_section:
461                     match = self.inc_line.match(line)
462
463                     if match is not None:
464                         inc_sect = match.group(1)
465                         log.debug("Found include directive %%%s%%" % inc_sect)
466
467                         try:
468                             self.__includes[inc_sect]
469                         except KeyError:
470                             for inc in self.__include_files:
471
472                                 if self.read_section(inc, inc_sect, True):
473                                     break
474                             else:
475                                 log.error("Include %%%s%% not found." % inc_sect)
476
477             if in_section:
478                 match = self.eq.search(line)
479
480                 if match is not None:
481                     key = match.group(1)
482                     value = match.group(2)
483                     value = self.convert_data(key, value)
484                     cache[read_section][key] = value
485
486         fd.close()
487         return found
488
489
490     def reset_includes(self):
491         self.__include_files = []
492         self.__includes = {}
493
494
495     def __getitem__(self, model):
496         model = model.lower()
497
498         try:
499             return self.__cache[model]
500         except:
501             log.debug("Cache miss: %s" % model)
502
503             log.debug("Reading file: %s" % self.released_dat)
504
505             if self.read_section(self.released_dat, model):
506                 return self.__cache[model]
507
508             if self.unreleased_dat is not None and os.path.exists(self.unreleased_dat):
509                 log.debug("Reading file: %s" % self.unreleased_dat)
510
511                 if self.read_section(self.unreleased_dat, model):
512                     return self.__cache[model]
513
514             return {}
515
516
517     def all_models(self):
518         return self.__cache
519
520
521     def get_data_type(self, key):
522         try:
523             return self.FIELD_TYPES[key]
524         except KeyError:
525             try:
526                 return self.FIELD_TYPES_DYN[key]
527             except KeyError:
528                 try:
529                     return self.TYPE_CACHE[key]
530                 except KeyError:
531                     for pat, typ in self.RE_FIELD_TYPES.items():
532                         match = pat.match(key)
533                         if match is not None:
534                             self.TYPE_CACHE[key] = typ
535                             return typ
536
537         log.error("get_data_type(): Field type lookup failed for key %s" % key)
538         return None
539
540
541     def convert_data(self, key, value, typ=None):
542         if typ is None:
543             typ = self.get_data_type(key)
544
545         if  typ in (TYPE_BITFIELD, TYPE_INT):
546             try:
547                 value = int(value)
548             except (ValueError, TypeError):
549                 log.error("Invalid value in .dat file: %s=%s" % (key, value))
550                 value = 0
551
552         elif typ == TYPE_BOOL:
553             value = utils.to_bool(value)
554
555         elif typ == TYPE_LIST:
556             value = [x for x in value.split(',') if x]
557
558         elif typ == TYPE_DATE: # mm/dd/yyyy
559             if datetime_avail:
560                 # ...don't use datetime.strptime(), wasn't avail. until 2.5
561                 match = self.date.search(value)
562
563                 if match is not None:
564                     month = int(match.group(1))
565                     day = int(match.group(2))
566                     year = int(match.group(3))
567
568                     value = datetime.date(year, month, day)
569
570         elif typ == TYPE_HEX:
571             try:
572                 value = int(value, 16)
573             except (ValueError, TypeError):
574                 log.error("Invalid hex value in .dat file: %s=%s" % (key, value))
575                 value = 0
576
577         return value