home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / hplip / base / utils.py < prev    next >
Encoding:
Python Source  |  2006-08-30  |  37.4 KB  |  1,275 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # (c) Copyright 2001-2006 Hewlett-Packard Development Company, L.P.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  18. #
  19. # Author: Don Welch
  20. #
  21. # Thanks to Henrique M. Holschuh <hmh@debian.org> for various security patches
  22. #
  23.  
  24. from __future__ import generators
  25.  
  26. # Std Lib
  27. import sys, os, fnmatch, tempfile, socket, struct, select, time
  28. import fcntl, errno, stat, string, xml.parsers.expat, commands
  29.  
  30. # Local
  31. from g import *
  32. from codes import *
  33.  
  34.  
  35. def Translator(frm='', to='', delete='', keep=None):
  36.     allchars = string.maketrans('','')
  37.  
  38.     if len(to) == 1:
  39.         to = to * len(frm)
  40.     trans = string.maketrans(frm, to)
  41.  
  42.     if keep is not None:
  43.         delete = allchars.translate(allchars, keep.translate(allchars, delete))
  44.  
  45.     def callable(s):
  46.         return s.translate(trans, delete)
  47.  
  48.     return callable
  49.  
  50. # For pidfile locking (must be "static" and global to the whole app)
  51. prv_pidfile = None
  52. prv_pidfile_name = ""
  53.  
  54.  
  55. def get_pidfile_lock (a_pidfile_name=""):
  56.     """ Call this to either lock the pidfile, or to update it after a fork()
  57.         Credit: Henrique M. Holschuh <hmh@debian.org>
  58.     """
  59.     global prv_pidfile
  60.     global prv_pidfile_name
  61.     if prv_pidfile_name == "":
  62.         try:
  63.             prv_pidfile_name = a_pidfile_name
  64.             prv_pidfile = os.fdopen(os.open(prv_pidfile_name, os.O_RDWR | os.O_CREAT, 0644), 'r+')
  65.             fcntl.fcntl(prv_pidfile.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
  66.             while 1:
  67.                 try:
  68.                     fcntl.flock(prv_pidfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
  69.                 except (OSError, IOError), e:
  70.                     if e.errno == errno.EINTR:
  71.                         continue
  72.                     elif e.errno == errno.EWOULDBLOCK:
  73.                         try:
  74.                             prv_pidfile.seek(0)
  75.                             otherpid = int(prv_pidfile.readline(), 10)
  76.                             sys.stderr.write ("can't lock %s, running daemon's pid may be %d\n" % (prv_pidfile_name, otherpid))
  77.                         except (OSError, IOError), e:
  78.                             sys.stderr.write ("error reading pidfile %s: (%d) %s\n" % (prv_pidfile_name, e.errno, e.strerror))
  79.  
  80.                         sys.exit(1)
  81.                     sys.stderr.write ("can't lock %s: (%d) %s\n" % (prv_pidfile_name, e.errno, e.strerror))
  82.                     sys.exit(1)
  83.                 break
  84.         except (OSError, IOError), e:
  85.             sys.stderr.write ("can't open pidfile %s: (%d) %s\n" % (prv_pidfile_name, e.errno, e.strerror))
  86.             sys.exit(1)
  87.     try:
  88.         prv_pidfile.seek(0)
  89.         prv_pidfile.write("%d\n" % (os.getpid()))
  90.         prv_pidfile.flush()
  91.         prv_pidfile.truncate()
  92.     except (OSError, IOError), e:
  93.         log.error("can't update pidfile %s: (%d) %s\n" % (prv_pidfile_name, e.errno, e.strerror))
  94.  
  95.  
  96.  
  97. def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
  98.     """
  99.     Credit: J├╝rgen Hermann, Andy Gimblett, and Noah Spurrier
  100.             http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
  101.  
  102.     Proper pidfile support: Henrique M. Holschuh <hmh@debian.org>
  103.     """
  104.     # Try to lock pidfile if not locked already
  105.     if prv_pidfile_name != '' or prv_pidfile_name != "":
  106.         get_pidfile_lock(prv_pidfile_name)
  107.  
  108.     # Do first fork.
  109.     try:
  110.         pid = os.fork()
  111.         if pid > 0:
  112.             sys.exit(0) # Exit first parent.
  113.     except OSError, e:
  114.         sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
  115.         sys.exit(1)
  116.  
  117.     # Decouple from parent environment.
  118.     os.chdir("/")
  119.     os.umask(0)
  120.     os.setsid()
  121.  
  122.     # Do second fork.
  123.     try:
  124.         pid = os.fork()
  125.         if pid > 0:
  126.             sys.exit(0) # Exit second parent.
  127.     except OSError, e:
  128.         sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
  129.         sys.exit(1)
  130.  
  131.     if prv_pidfile_name != "":
  132.         get_pidfile_lock()
  133.  
  134.     # Now I am a daemon!
  135.  
  136.     # Redirect standard file descriptors.
  137.     si = file(stdin, 'r')
  138.     so = file(stdout, 'a+')
  139.     se = file(stderr, 'a+', 0)
  140.     os.dup2(si.fileno(), sys.stdin.fileno())
  141.     os.dup2(so.fileno(), sys.stdout.fileno())
  142.     os.dup2(se.fileno(), sys.stderr.fileno())
  143.  
  144.  
  145.  
  146. def ifelse(cond, t, f):
  147.     if cond: return t
  148.     else: return f
  149.  
  150. def to_bool_str(s, default='0'):
  151.     """ Convert an arbitrary 0/1/T/F/Y/N string to a normalized string 0/1."""
  152.     if isinstance(s, str) and s:
  153.         if s[0].lower() in ['1', 't', 'y']:
  154.             return '1'
  155.         elif s[0].lower() in ['0', 'f', 'n']:
  156.             return '0'
  157.  
  158.     return default
  159.  
  160. def to_bool(s, default=False):
  161.     """ Convert an arbitrary 0/1/T/F/Y/N string to a boolean True/False value."""
  162.     if isinstance(s, str) and s:
  163.         if s[0].lower() in ['1', 't', 'y']:
  164.             return True
  165.         elif s[0].lower() in ['0', 'f', 'n']:
  166.             return False
  167.     elif isinstance(s, bool):
  168.         return s
  169.  
  170.     return default
  171.  
  172. def path_exists_safely(path):
  173.     """ Returns True if path exists, and points to a file with permissions at least as strict as 0755.
  174.         Credit: Contributed by Henrique M. Holschuh <hmh@debian.org>"""
  175.     try:
  176.         pathmode = os.stat(path)[stat.ST_MODE]
  177.         if pathmode & 0022 != 0:
  178.             return False
  179.     except (IOError,OSError):
  180.         return False
  181.     return True
  182.  
  183.  
  184. def walkFiles(root, recurse=True, abs_paths=False, return_folders=False, pattern='*', path=None):
  185.     if path is None:
  186.         path = root
  187.  
  188.     try:
  189.         names = os.listdir(root)
  190.     except os.error:
  191.         raise StopIteration
  192.  
  193.     pattern = pattern or '*'
  194.     pat_list = pattern.split(';')
  195.  
  196.     for name in names:
  197.         fullname = os.path.normpath(os.path.join(root, name))
  198.  
  199.         for pat in pat_list:
  200.             if fnmatch.fnmatch(name, pat):
  201.                 if return_folders or not os.path.isdir(fullname):
  202.                     if abs_paths:
  203.                         yield fullname
  204.                     else:
  205.                         try:
  206.                             yield os.path.basename(fullname)
  207.                         except ValueError:
  208.                             yield fullname
  209.  
  210.         if os.path.islink(fullname):
  211.             fullname = os.path.realpath(os.readlink(fullname))
  212.  
  213.         if recurse and os.path.isdir(fullname) or os.path.islink(fullname):
  214.             for f in walkFiles(fullname, recurse, abs_paths, return_folders, pattern, path):
  215.                 yield f
  216.  
  217.  
  218. def is_path_writable(path):
  219.     if os.path.exists(path):
  220.         s = os.stat(path)
  221.         mode = s[stat.ST_MODE] & 0777
  222.  
  223.         if mode & 02:
  224.             return True
  225.         elif s[stat.ST_GID] == os.getgid() and mode & 020:
  226.             return True
  227.         elif s[stat.ST_UID] == os.getuid() and mode & 0200:
  228.             return True
  229.  
  230.     return False
  231.  
  232.  
  233. # Provides the TextFormatter class for formatting text into columns.
  234. # Original Author: Hamish B Lawson, 1999
  235. # Modified by: Don Welch, 2003
  236. class TextFormatter:
  237.  
  238.     LEFT  = 0
  239.     CENTER = 1
  240.     RIGHT  = 2
  241.  
  242.     def __init__(self, colspeclist):
  243.         self.columns = []
  244.         for colspec in colspeclist:
  245.             self.columns.append(Column(**colspec))
  246.  
  247.     def compose(self, textlist, add_newline=False):
  248.         numlines = 0
  249.         textlist = list(textlist)
  250.         if len(textlist) != len(self.columns):
  251.             log.error("Formatter: Number of text items does not match columns")
  252.             return
  253.         for text, column in map(None, textlist, self.columns):
  254.             column.wrap(text)
  255.             numlines = max(numlines, len(column.lines))
  256.         complines = [''] * numlines
  257.         for ln in range(numlines):
  258.             for column in self.columns:
  259.                 complines[ln] = complines[ln] + column.getline(ln)
  260.         if add_newline:
  261.             return '\n'.join(complines) + '\n'
  262.         else:
  263.             return '\n'.join(complines)
  264.  
  265.     def bold(text):
  266.         return ''.join(["\033[1m", text, "\033[0m"])
  267.  
  268.     bold = staticmethod(bold)
  269.  
  270.  
  271. class Column:
  272.  
  273.     def __init__(self, width=78, alignment=TextFormatter.LEFT, margin=0):
  274.         self.width = width
  275.         self.alignment = alignment
  276.         self.margin = margin
  277.         self.lines = []
  278.  
  279.     def align(self, line):
  280.         if self.alignment == TextFormatter.CENTER:
  281.             return line.center(self.width)
  282.         elif self.alignment == TextFormatter.RIGHT:
  283.             return line.rjust(self.width)
  284.         else:
  285.             return line.ljust(self.width)
  286.  
  287.     def wrap(self, text):
  288.         self.lines = []
  289.         words = []
  290.         for word in text.split():
  291.             if word <= self.width:
  292.                 words.append(word)
  293.             else:
  294.                 for i in range(0, len(word), self.width):
  295.                     words.append(word[i:i+self.width])
  296.         if not len(words): return
  297.         current = words.pop(0)
  298.         for word in words:
  299.             increment = 1 + len(word)
  300.             if len(current) + increment > self.width:
  301.                 self.lines.append(self.align(current))
  302.                 current = word
  303.             else:
  304.                 current = current + ' ' + word
  305.         self.lines.append(self.align(current))
  306.  
  307.     def getline(self, index):
  308.         if index < len(self.lines):
  309.             return ' '*self.margin + self.lines[index]
  310.         else:
  311.             return ' ' * (self.margin + self.width)
  312.  
  313.  
  314.  
  315.  
  316.  
  317. class Stack:
  318.     def __init__(self):
  319.         self.stack = []
  320.  
  321.     def pop(self):
  322.         return self.stack.pop()
  323.  
  324.     def push(self, value):
  325.         self.stack.append(value)
  326.  
  327.     def as_list(self):
  328.         return self.stack
  329.  
  330.     def clear(self):
  331.         self.stack = []
  332.  
  333.  
  334. # RingBuffer class
  335. # Source: Python Cookbook 1st Ed., sec. 5.18, pg. 201
  336. # Credit: Sebastien Keim
  337. # License: Modified BSD
  338. class RingBuffer:
  339.     def __init__(self,size_max=50):
  340.         self.max = size_max
  341.         self.data = []
  342.     def append(self,x):
  343.         """append an element at the end of the buffer"""
  344.         self.data.append(x)
  345.         if len(self.data) == self.max:
  346.             self.cur=0
  347.             self.__class__ = RingBufferFull
  348.     def get(self):
  349.         """ return a list of elements from the oldest to the newest"""
  350.         return self.data
  351.  
  352.  
  353. class RingBufferFull:
  354.     def __init__(self,n):
  355.         #raise "you should use RingBuffer"
  356.         pass
  357.     def append(self,x):
  358.         self.data[self.cur]=x
  359.         self.cur=(self.cur+1) % self.max
  360.     def get(self):
  361.         return self.data[self.cur:]+self.data[:self.cur]
  362.  
  363.  
  364. # CRC routines for RP
  365. if 0:
  366.     def updateCRC(crc, ch):
  367.         ch = ord(ch)
  368.         for i in range(8):
  369.             if ((crc ^ ch) & 1):
  370.                 crc = (crc >> 1) ^ 0xa001
  371.             else:
  372.                 crc = crc >> 1
  373.             ch = ch >> 1
  374.  
  375.         return crc
  376.  
  377.  
  378.  
  379. # 16-bit CRCs should detect 65535/65536 or 99.998% of all errors in
  380. # data blocks up to 4096 bytes
  381. MASK_CCITT  = 0x1021     # CRC-CCITT mask (ISO 3309, used in X25, HDLC)
  382. MASK_CRC16  = 0xA001     # CRC16 mask (used in ARC files)
  383.  
  384. #----------------------------------------------------------------------------
  385. # Calculate and return an incremental CRC value based on the current value
  386. # and the data bytes passed in as a string.
  387. #
  388. def updateCRC(crc, data, mask=MASK_CRC16):
  389.  
  390.     for char in data:
  391.         c = ord(char)
  392.         c = c << 8L
  393.  
  394.         for j in xrange(8):
  395.             if (crc ^ c) & 0x8000L:
  396.                 crc = (crc << 1L) ^ mask
  397.             else:
  398.                 crc = crc << 1L
  399.             c = c << 1L
  400.  
  401.     return crc & 0xffffL
  402.  
  403. def calcCRC(data):
  404.     crc = 0
  405.     #for c in data:
  406.     #    crc = updateCRC( crc, c )
  407.  
  408.     crc = updateCRC(crc, data)
  409.  
  410.     if crc == 0:
  411.         crc = len(data)
  412.  
  413.     return crc
  414.  
  415.  
  416.  
  417.  
  418. def sort_dict_by_value(d):
  419.     """ Returns the keys of dictionary d sorted by their values """
  420.     items=d.items()
  421.     backitems=[[v[1],v[0]] for v in items]
  422.     backitems.sort()
  423.     return [backitems[i][1] for i in range(0,len(backitems))]
  424.  
  425.  
  426. # Copied from Gentoo Portage output.py
  427. # Copyright 1998-2003 Daniel Robbins, Gentoo Technologies, Inc.
  428. # Distributed under the GNU Public License v2
  429.  
  430. codes={}
  431. codes["reset"]="\x1b[0m"
  432. codes["bold"]="\x1b[01m"
  433.  
  434. codes["teal"]="\x1b[36;06m"
  435. codes["turquoise"]="\x1b[36;01m"
  436.  
  437. codes["fuscia"]="\x1b[35;01m"
  438. codes["purple"]="\x1b[35;06m"
  439.  
  440. codes["blue"]="\x1b[34;01m"
  441. codes["darkblue"]="\x1b[34;06m"
  442.  
  443. codes["green"]="\x1b[32;01m"
  444. codes["darkgreen"]="\x1b[32;06m"
  445.  
  446. codes["yellow"]="\x1b[33;01m"
  447. codes["brown"]="\x1b[33;06m"
  448.  
  449. codes["red"]="\x1b[31;01m"
  450. codes["darkred"]="\x1b[31;06m"
  451.  
  452.  
  453. def bold(text):
  454.     return codes["bold"]+text+codes["reset"]
  455.  
  456. def white(text):
  457.     return bold(text)
  458.  
  459. def teal(text):
  460.     return codes["teal"]+text+codes["reset"]
  461.  
  462. def turquoise(text):
  463.     return codes["turquoise"]+text+codes["reset"]
  464.  
  465. def darkteal(text):
  466.     return turquoise(text)
  467.  
  468. def fuscia(text):
  469.     return codes["fuscia"]+text+codes["reset"]
  470.  
  471. def purple(text):
  472.     return codes["purple"]+text+codes["reset"]
  473.  
  474. def blue(text):
  475.     return codes["blue"]+text+codes["reset"]
  476.  
  477. def darkblue(text):
  478.     return codes["darkblue"]+text+codes["reset"]
  479.  
  480. def green(text):
  481.     return codes["green"]+text+codes["reset"]
  482.  
  483. def darkgreen(text):
  484.     return codes["darkgreen"]+text+codes["reset"]
  485.  
  486. def yellow(text):
  487.     return codes["yellow"]+text+codes["reset"]
  488.  
  489. def brown(text):
  490.     return codes["brown"]+text+codes["reset"]
  491.  
  492. def darkyellow(text):
  493.     return brown(text)
  494.  
  495. def red(text):
  496.     return codes["red"]+text+codes["reset"]
  497.  
  498. def darkred(text):
  499.     return codes["darkred"]+text+codes["reset"]
  500.  
  501.  
  502. def commafy(val):
  503.     return val < 0 and '-' + commafy(abs(val)) \
  504.         or val < 1000 and str(val) \
  505.         or '%s,%03d' % (commafy(val / 1000), (val % 1000))
  506.  
  507.  
  508. def format_bytes(s, show_bytes=False):
  509.     if s < 1024:
  510.         return ''.join([commafy(s), ' B'])
  511.     elif 1024 < s < 1048576:
  512.         if show_bytes:
  513.             return ''.join([str(round(s/1024.0, 1)) , ' KB (',  commafy(s), ')'])
  514.         else:
  515.             return ''.join([str(round(s/1024.0, 1)) , ' KB'])
  516.     else:
  517.         if show_bytes:
  518.             return ''.join([str(round(s/1048576.0, 1)), ' MB (',  commafy(s), ')'])
  519.         else:
  520.             return ''.join([str(round(s/1048576.0, 1)), ' MB'])
  521.  
  522.  
  523.  
  524. try:
  525.     make_temp_file = tempfile.mkstemp # 2.3+
  526. except AttributeError:
  527.     def make_temp_file(suffix='', prefix='', dir='', text=False): # pre-2.3
  528.         path = tempfile.mktemp(suffix)
  529.         fd = os.open(path, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
  530.         #os.unlink( path ) # TODO... make this secure
  531.         return ( os.fdopen( fd, 'w+b' ), path )
  532.         #return (fd, path)
  533.  
  534. def log_title(program_name, version):
  535.     log.info("")
  536.     log.info(bold("HP Linux Imaging and Printing System (ver. %s)" % prop.version))
  537.     log.info(bold("%s ver. %s" % (program_name,version)))
  538.     log.info("")
  539.     log.info("Copyright (c) 2003-6 Hewlett-Packard Development Company, LP")
  540.     log.info("This software comes with ABSOLUTELY NO WARRANTY.")
  541.     log.info("This is free software, and you are welcome to distribute it")
  542.     log.info("under certain conditions. See COPYING file for more details.")
  543.     log.info("")
  544.  
  545.  
  546. def which(command):
  547.     path = os.getenv('PATH').split(':')
  548.     found_path = ''
  549.     for p in path:
  550.         try:
  551.             files = os.listdir(p)
  552.         except:
  553.             continue
  554.         else:
  555.             if command in files:
  556.                 found_path = p
  557.                 break
  558.  
  559.     return found_path
  560.  
  561.  
  562. def deviceDefaultFunctions():
  563.     cmd_print, cmd_copy, cmd_fax, \
  564.         cmd_pcard, cmd_scan, cmd_fab = \
  565.         '', '', '', '', '', ''
  566.  
  567.     # Print
  568.     path = which('hp-print')
  569.  
  570.     if len(path) > 0:
  571.         cmd_print = 'hp-print -p%PRINTER%'
  572.     else:
  573.         path = which('kprinter')
  574.  
  575.         if len(path) > 0:
  576.             cmd_print = 'kprinter -P%PRINTER% --system cups'
  577.         else:
  578.             path = which('gtklp')
  579.  
  580.             if len(path) > 0:
  581.                 cmd_print = 'gtklp -P%PRINTER%'
  582.  
  583.             else:
  584.                 path = which('xpp')
  585.  
  586.                 if len(path) > 0:
  587.                     cmd_print = 'xpp -P%PRINTER%'
  588.  
  589.  
  590.     # Scan
  591.     path = which('xsane')
  592.  
  593.     if len(path) > 0:
  594.         cmd_scan = 'xsane -V %SANE_URI%'
  595.     else:
  596.         path = which('kooka')
  597.  
  598.         if len(path)>0:
  599.             #cmd_scan = 'kooka -d "%SANE_URI%"'
  600.             cmd_scan = 'kooka'
  601.  
  602.         else:
  603.             path = which('xscanimage')
  604.  
  605.             if len(path)>0:
  606.                 cmd_scan = 'xscanimage'
  607.  
  608.     # Photo Card
  609.     path = which('hp-unload')
  610.  
  611.     if len(path):
  612.         cmd_pcard = 'hp-unload -d %DEVICE_URI%'
  613.  
  614.     else:
  615.         cmd_pcard = 'python %HOME%/unload.py -d %DEVICE_URI%'
  616.  
  617.     # Copy
  618.  
  619.     # Fax
  620.     path = which('hp-sendfax')
  621.  
  622.     if len(path):
  623.         cmd_fax = 'hp-sendfax -d %FAX_URI%'
  624.  
  625.     else:
  626.         cmd_fax = 'python %HOME%/sendfax.py -d %FAX_URI%'
  627.  
  628.     
  629.     # Fax Address Book
  630.     path = which('hp-fab')
  631.  
  632.     if len(path):
  633.         cmd_fab = 'hp-fab'
  634.  
  635.     else:
  636.         cmd_fab = 'python %HOME%/fab.py'
  637.  
  638.     
  639.     
  640.     return cmd_print, cmd_scan, cmd_pcard, \
  641.            cmd_copy, cmd_fax, cmd_fab
  642.  
  643.  
  644.  
  645. def checkPyQtImport():
  646.     # PyQt
  647.     try:
  648.         import qt
  649.     except ImportError:
  650.         log.error("PyQt not installed. GUI not available. Exiting.")
  651.         return False
  652.  
  653.     # check version of Qt
  654.     qtMajor = int(qt.qVersion().split('.')[0])
  655.  
  656.     if qtMajor < MINIMUM_QT_MAJOR_VER:
  657.  
  658.         log.error("Incorrect version of Qt installed. Ver. 3.0.0 or greater required.")
  659.         return False
  660.  
  661.     #check version of PyQt
  662.     try:
  663.         pyqtVersion = qt.PYQT_VERSION_STR
  664.     except:
  665.         pyqtVersion = qt.PYQT_VERSION
  666.  
  667.     while pyqtVersion.count('.') < 2:
  668.         pyqtVersion += '.0'
  669.  
  670.     (maj_ver, min_ver, pat_ver) = pyqtVersion.split('.')
  671.  
  672.     if pyqtVersion.find('snapshot') >= 0:
  673.         log.warning("A non-stable snapshot version of PyQt is installed.")
  674.     else:
  675.         try:
  676.             maj_ver = int(maj_ver)
  677.             min_ver = int(min_ver)
  678.             pat_ver = int(pat_ver)
  679.         except ValueError:
  680.             maj_ver, min_ver, pat_ver = 0, 0, 0
  681.  
  682.         if maj_ver < MINIMUM_PYQT_MAJOR_VER or \
  683.             (maj_ver == MINIMUM_PYQT_MAJOR_VER and min_ver < MINIMUM_PYQT_MINOR_VER):
  684.             log.error("This program may not function properly with the version of PyQt that is installed (%d.%d.%d)." % (maj_ver, min_ver, pat_ver))
  685.             log.error("Incorrect version of pyQt installed. Ver. %d.%d or greater required." % (MINIMUM_PYQT_MAJOR_VER, MINIMUM_PYQT_MINOR_VER))
  686.             return False
  687.  
  688.     return True
  689.  
  690.  
  691. def loadTranslators(app, user_config):
  692.     #from qt import *
  693.     import qt
  694.     loc = None
  695.  
  696.     if os.path.exists(user_config):
  697.         # user_config contains executables we will run, so we
  698.         # must make sure it is a safe file, and refuse to run
  699.         # otherwise.
  700.         if not path_exists_safely(user_config):
  701.             log.warning("File %s has insecure permissions! File ignored." % user_config)
  702.         else:
  703.             config = ConfigParser.ConfigParser()
  704.             config.read(user_config)
  705.  
  706.             if config.has_section("ui"):
  707.                 loc = config.get("ui", "loc")
  708.  
  709.                 if not loc:
  710.                     loc = None
  711.  
  712.     if loc is not None:
  713.  
  714.         if loc.lower() == 'system':
  715.             loc = str(qt.QTextCodec.locale())
  716.  
  717.         if loc.lower() != 'c':
  718.  
  719.             log.debug("Trying to load .qm file for %s locale." % loc)
  720.  
  721.             dirs = [prop.home_dir, prop.data_dir, prop.i18n_dir]
  722.  
  723.             trans = qt.QTranslator(None)
  724.  
  725.             for dir in dirs:
  726.                 qm_file = 'hplip_%s' % loc
  727.                 loaded = trans.load(qm_file, dir)
  728.  
  729.                 if loaded:
  730.                     app.installTranslator(trans)
  731.                     break
  732.         else:
  733.             loc = None
  734.  
  735.     if loc is None:
  736.         log.debug("Using default 'C' locale")
  737.     else:
  738.         log.debug("Using locale: %s" % loc)
  739.  
  740.     return loc
  741.  
  742. try:
  743.     from string import Template # will fail in Python <= 2.3
  744. except ImportError:
  745.     # Code from Python 2.4 string.py
  746.     import re as _re
  747.  
  748.     class _multimap:
  749.         """Helper class for combining multiple mappings.
  750.  
  751.         Used by .{safe_,}substitute() to combine the mapping and keyword
  752.         arguments.
  753.         """
  754.         def __init__(self, primary, secondary):
  755.             self._primary = primary
  756.             self._secondary = secondary
  757.  
  758.         def __getitem__(self, key):
  759.             try:
  760.                 return self._primary[key]
  761.             except KeyError:
  762.                 return self._secondary[key]
  763.  
  764.  
  765.     class _TemplateMetaclass(type):
  766.         pattern = r"""
  767.         %(delim)s(?:
  768.           (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
  769.           (?P<named>%(id)s)      |   # delimiter and a Python identifier
  770.           {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
  771.           (?P<invalid>)              # Other ill-formed delimiter exprs
  772.         )
  773.         """
  774.  
  775.         def __init__(cls, name, bases, dct):
  776.             super(_TemplateMetaclass, cls).__init__(name, bases, dct)
  777.             if 'pattern' in dct:
  778.                 pattern = cls.pattern
  779.             else:
  780.                 pattern = _TemplateMetaclass.pattern % {
  781.                     'delim' : _re.escape(cls.delimiter),
  782.                     'id'    : cls.idpattern,
  783.                     }
  784.             cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
  785.  
  786.  
  787.     class Template:
  788.         """A string class for supporting $-substitutions."""
  789.         __metaclass__ = _TemplateMetaclass
  790.  
  791.         delimiter = '$'
  792.         idpattern = r'[_a-z][_a-z0-9]*'
  793.  
  794.         def __init__(self, template):
  795.             self.template = template
  796.  
  797.         # Search for $$, $identifier, ${identifier}, and any bare $'s
  798.  
  799.         def _invalid(self, mo):
  800.             i = mo.start('invalid')
  801.             lines = self.template[:i].splitlines(True)
  802.             if not lines:
  803.                 colno = 1
  804.                 lineno = 1
  805.             else:
  806.                 colno = i - len(''.join(lines[:-1]))
  807.                 lineno = len(lines)
  808.             raise ValueError('Invalid placeholder in string: line %d, col %d' %
  809.                              (lineno, colno))
  810.  
  811.         def substitute(self, *args, **kws):
  812.             if len(args) > 1:
  813.                 raise TypeError('Too many positional arguments')
  814.             if not args:
  815.                 mapping = kws
  816.             elif kws:
  817.                 mapping = _multimap(kws, args[0])
  818.             else:
  819.                 mapping = args[0]
  820.             # Helper function for .sub()
  821.             def convert(mo):
  822.                 # Check the most common path first.
  823.                 named = mo.group('named') or mo.group('braced')
  824.                 if named is not None:
  825.                     val = mapping[named]
  826.                     # We use this idiom instead of str() because the latter will
  827.                     # fail if val is a Unicode containing non-ASCII characters.
  828.                     return '%s' % val
  829.                 if mo.group('escaped') is not None:
  830.                     return self.delimiter
  831.                 if mo.group('invalid') is not None:
  832.                     self._invalid(mo)
  833.                 raise ValueError('Unrecognized named group in pattern',
  834.                                  self.pattern)
  835.             return self.pattern.sub(convert, self.template)
  836.  
  837.         def safe_substitute(self, *args, **kws):
  838.             if len(args) > 1:
  839.                 raise TypeError('Too many positional arguments')
  840.             if not args:
  841.                 mapping = kws
  842.             elif kws:
  843.                 mapping = _multimap(kws, args[0])
  844.             else:
  845.                 mapping = args[0]
  846.             # Helper function for .sub()
  847.             def convert(mo):
  848.                 named = mo.group('named')
  849.                 if named is not None:
  850.                     try:
  851.                         # We use this idiom instead of str() because the latter
  852.                         # will fail if val is a Unicode containing non-ASCII
  853.                         return '%s' % mapping[named]
  854.                     except KeyError:
  855.                         return self.delimiter + named
  856.                 braced = mo.group('braced')
  857.                 if braced is not None:
  858.                     try:
  859.                         return '%s' % mapping[braced]
  860.                     except KeyError:
  861.                         return self.delimiter + '{' + braced + '}'
  862.                 if mo.group('escaped') is not None:
  863.                     return self.delimiter
  864.                 if mo.group('invalid') is not None:
  865.                     return self.delimiter
  866.                 raise ValueError('Unrecognized named group in pattern',
  867.                                  self.pattern)
  868.             return self.pattern.sub(convert, self.template)
  869.  
  870.  
  871.  
  872. cat = lambda _ : Template(_).substitute(sys._getframe(1).f_globals, **sys._getframe(1).f_locals)
  873.  
  874.  
  875. class ModelParser:
  876.  
  877.     def __init__(self):
  878.         self.model = None
  879.         self.cur_model = None
  880.         self.stack = []
  881.         self.in_model = False
  882.         self.models = {}
  883.  
  884.     def startElement(self, name, attrs):
  885.         if name == 'models':
  886.             return
  887.  
  888.         elif name == 'model':
  889.             self.model = {}
  890.             self.cur_model = str(attrs['name']).replace('_', ' ').strip() #.replace('HP', '').replace('hp', '').strip()
  891.             self.in_model = True
  892.             self.stack = []
  893.  
  894.         else:
  895.             self.stack.append(str(name).lower())
  896.             if len(attrs):
  897.                 for a in attrs:
  898.                     self.stack.append(str(a).lower())
  899.                     try:
  900.                         i = int(attrs[a])
  901.                     except ValueError:
  902.                         i = str(attrs[a])
  903.  
  904.                     self.model[str('-'.join(self.stack))] = i
  905.                     self.stack.pop()
  906.  
  907.  
  908.     def endElement(self, name):
  909.         if name == 'model':
  910.             self.in_model = False
  911.  
  912.             if self.cur_model in self.models:
  913.                 log.error("Duplicate model in XML: %s" % self.cur_model)
  914.                 raise Error(ERROR_INTERNAL)
  915.  
  916.             print self.cur_model
  917.             self.models[self.cur_model] = self.model
  918.  
  919.             self.model = None
  920.         elif name == 'models':
  921.             return
  922.         else:
  923.             self.stack.pop()
  924.  
  925.  
  926.     def charData(self, data):
  927.         data = str(data).strip()
  928.         if data and self.model is not None and self.stack:
  929.             self.model[str('-'.join(self.stack))] = str(data)
  930.  
  931.     def loadModels(self, filename, untested=False):
  932.         parser = xml.parsers.expat.ParserCreate()
  933.         parser.StartElementHandler = self.startElement
  934.         parser.EndElementHandler = self.endElement
  935.         parser.CharacterDataHandler = self.charData
  936.         try:
  937.             parser.Parse(open(filename).read(), True)
  938.         except xml.parsers.expat.ExpatError, e:
  939.             log.error("XML file parse error: %s" % e)
  940.             raise Error(ERROR_INTERNAL)
  941.  
  942.         return self.models
  943.  
  944.  
  945. identity = string.maketrans('','')
  946. unprintable = identity.translate(identity, string.printable)
  947.  
  948. def printable(s):
  949.     return s.translate(identity, unprintable)
  950.  
  951.  
  952. def any(S,f=lambda x:x):
  953.     for x in S:
  954.         if f(x): return True
  955.     return False
  956.  
  957. def all(S,f=lambda x:x):
  958.     for x in S:
  959.         if not f(x): return False
  960.     return True
  961.  
  962. def openURL(url):
  963.     browsers = ['firefox', 'mozilla', 'konqueror', 'galeon', 'skipstone']
  964.     for b in browsers:
  965.         if which(b):
  966.             cmd = "%s %s &" % (b, url)
  967.             log.debug(cmd)
  968.             os.system(cmd)
  969.             break
  970.     else:
  971.         log.warn("Unable to open URL: %s" % url)
  972.         
  973.         
  974. def uniqueList(input):
  975.     temp = []
  976.     [temp.append(i) for i in input if not temp.count(i)]
  977.     return temp
  978.  
  979.     
  980. def list_move_up(l, m):
  981.     for i in range(1, len(l)):
  982.         if l[i] == m:
  983.             l[i-1],l[i] = l[i],l[i-1]
  984.  
  985.             
  986. def list_move_down(l, m):
  987.     for i in range(len(l) - 2, 0, -1):
  988.         if l[i] == m:
  989.             l[i],l[i+1] = l[i+1],l[i] 
  990.             
  991.  
  992. def levenshtein_distance(a,b):
  993.     """
  994.     Calculates the Levenshtein distance between a and b.
  995.     Written by Magnus Lie Hetland.
  996.     """
  997.     n, m = len(a), len(b)
  998.     if n > m:
  999.         a,b = b,a
  1000.         n,m = m,n
  1001.         
  1002.     current = range(n+1)
  1003.     for i in range(1,m+1):
  1004.         previous, current = current, [i]+[0]*m
  1005.         for j in range(1,n+1):
  1006.             add, delete = previous[j]+1, current[j-1]+1
  1007.             change = previous[j-1]
  1008.             if a[j-1] != b[i-1]:
  1009.                 change = change + 1
  1010.             current[j] = min(add, delete, change)
  1011.             
  1012.     return current[n]
  1013.             
  1014.             
  1015. class XMLToDictParser:
  1016.     def __init__(self):
  1017.         self.stack = []
  1018.         self.data = {}
  1019.  
  1020.     def startElement(self, name, attrs):
  1021.         self.stack.append(str(name).lower())
  1022.         
  1023.         if len(attrs):
  1024.             for a in attrs:
  1025.                 self.stack.append(str(a).lower())
  1026.                 self.addData(attrs[a])
  1027.                 self.stack.pop()
  1028.  
  1029.     def endElement(self, name):
  1030.         self.stack.pop()
  1031.  
  1032.     def charData(self, data):
  1033.         data = str(data).strip()
  1034.  
  1035.         if data and self.stack:
  1036.             self.addData(data)
  1037.                 
  1038.     def addData(self, data):
  1039.         try:
  1040.             data = int(data)
  1041.         except ValueError:
  1042.             data = str(data)
  1043.         
  1044.         stack_str = '-'.join(self.stack)
  1045.         stack_str_0 = '-'.join([stack_str, '0'])
  1046.         
  1047.         try:
  1048.             self.data[stack_str]
  1049.         except KeyError:
  1050.             try:
  1051.                 self.data[stack_str_0]
  1052.             except KeyError:
  1053.                 self.data[stack_str] = data
  1054.             else:
  1055.                 j = 2
  1056.                 while True:
  1057.                     try:
  1058.                         self.data['-'.join([stack_str, str(j)])]
  1059.                     except KeyError:
  1060.                         self.data['-'.join([stack_str, str(j)])] = data
  1061.                         break
  1062.                     j += 1                    
  1063.                 
  1064.         else:
  1065.             self.data[stack_str_0] = self.data[stack_str]
  1066.             self.data['-'.join([stack_str, '1'])] = data
  1067.             del self.data[stack_str]
  1068.     
  1069.  
  1070.     def parseXML(self, text):
  1071.         parser = xml.parsers.expat.ParserCreate()
  1072.         parser.StartElementHandler = self.startElement
  1073.         parser.EndElementHandler = self.endElement
  1074.         parser.CharacterDataHandler = self.charData
  1075.         parser.Parse(text, True)
  1076.         return self.data
  1077.         
  1078.  
  1079.  # ------------------------- Usage Help
  1080.  
  1081. USAGE_OPTIONS = ("[OPTIONS]", "", "heading", False)
  1082. USAGE_LOGGING1 = ("Set the logging level:", "-l<level> or --logging=<level>", 'option', False)
  1083. USAGE_LOGGING2 = ("", "<level>: none, info*, error, warn, debug (\*default)", "option", False)
  1084. USAGE_LOGGING3 = ("Run in debug mode:", "-g (same as option: -ldebug)", "option", False)
  1085. USAGE_ARGS = ("[PRINTER|DEVICE-URI] (See Notes)", "", "heading", False)
  1086. USAGE_DEVICE = ("To specify a device-URI:", "-d<device-uri> or --device=<device-uri>", "option", False)
  1087. USAGE_PRINTER = ("To specify a CUPS printer:", "-p<printer> or --printer=<printer>", "option", False)
  1088. USAGE_BUS1 = ("Bus to probe (if device not specified):", "-b<bus> or --bus=<bus>", "option", False)
  1089. USAGE_BUS2 = ("", "<bus>: cups\*, usb*, net, bt, fw, par\* (\*default) (Note: bt and fw not supported in this release", 'option', False)
  1090. USAGE_HELP = ("This help information:", "-h or --help", "option", True)
  1091. USAGE_SPACE = ("", "", "space", False)
  1092. USAGE_EXAMPLES = ("Examples:", "", "heading", False)
  1093. USAGE_NOTES = ("Notes:", "", "heading", False)
  1094. USAGE_STD_NOTES1 = ("1. If device or printer is not specified, the local device bus is probed and the program enters interactive mode.", "", "note", False)
  1095. USAGE_STD_NOTES2 = ("2. If -p\* is specified, the default CUPS printer will be used.", "", "note", False)
  1096. USAGE_SEEALSO = ("See Also:", "", "heading", False)
  1097.  
  1098. def ttysize():
  1099.     ln1 = commands.getoutput('stty -a').splitlines()[0]
  1100.     vals = {'rows':None, 'columns':None}
  1101.     for ph in ln1.split(';'):
  1102.         x = ph.split()
  1103.         if len(x) == 2:
  1104.             vals[x[0]] = x[1]
  1105.             vals[x[1]] = x[0]
  1106.     return int(vals['rows']), int(vals['columns'])
  1107.  
  1108.  
  1109. def usage_formatter(override=0):
  1110.     rows, cols = ttysize()
  1111.     
  1112.     if override:
  1113.         col1 = override
  1114.         col2 = cols - col1 - 8
  1115.     else:
  1116.         col1 = int(cols / 3) - 8
  1117.         col2 = cols - col1 - 8
  1118.     
  1119.     return TextFormatter(({'width': col1, 'margin' : 2},
  1120.                             {'width': col2, 'margin' : 2},))
  1121.  
  1122.  
  1123. def format_text(text_list, typ='text', title='', crumb='', version=''):
  1124.     """
  1125.     Format usage text in multiple formats:
  1126.         text: for --help in the console
  1127.         rest: for conversion with rst2web for the website
  1128.         man: for manpages
  1129.     """
  1130.     if typ == 'text':
  1131.         formatter = usage_formatter()
  1132.         
  1133.         for line in text_list:
  1134.             text1, text2, format, trailing_space = line
  1135.             
  1136.             # remove any reST/man escapes
  1137.             text1 = text1.replace("\\", "")
  1138.             text2 = text2.replace("\\", "")
  1139.             
  1140.             if format == 'summary':
  1141.                 log.info(bold(text1))
  1142.                 log.info("")
  1143.             
  1144.             elif format in ('para', 'name', 'seealso'):
  1145.                 log.info(text1)
  1146.                 
  1147.                 if trailing_space:
  1148.                     log.info("")
  1149.             
  1150.             elif format in ('heading', 'header'):
  1151.                 log.info(bold(text1))
  1152.                 
  1153.             elif format in ('option', 'example'):
  1154.                 log.info(formatter.compose((text1, text2), trailing_space))
  1155.                 
  1156.             elif format == 'note':
  1157.                 if text1.startswith(' '):
  1158.                     log.info('\t' + text1.lstrip())
  1159.                 else:
  1160.                     log.info(text1)
  1161.                     
  1162.             elif format == 'space':
  1163.                 log.info("")
  1164.             
  1165.         log.info("")
  1166.         
  1167.                 
  1168.     elif typ == 'rest':
  1169.         colwidth1, colwidth2 = 0, 0
  1170.         for line in text_list:
  1171.             text1, text2, format, trailing_space = line
  1172.  
  1173.             if format in ('option', 'example'):
  1174.                 colwidth1 = max(len(text1), colwidth1)
  1175.                 colwidth2 = max(len(text2), colwidth2)
  1176.             
  1177.         colwidth1 += 3
  1178.         tablewidth = colwidth1 + colwidth2
  1179.         
  1180.         # write the rst2web header
  1181.         log.info("""restindex
  1182. page-title: %s
  1183. crumb: %s
  1184. format: rest
  1185. file-extension: html
  1186. encoding: utf8
  1187. /restindex\n""" % (title, crumb))
  1188.         
  1189.         links = []
  1190.         
  1191.         for line in text_list:
  1192.             text1, text2, format, trailing_space = line
  1193.  
  1194.             if format == 'seealso':
  1195.                 links.append(text1)
  1196.                 text1 = "`%s`_" % text1
  1197.  
  1198.             len1, len2 = len(text1), len(text2)
  1199.             
  1200.             if format == 'summary':
  1201.                 log.info(''.join(["**", text1, "**"]))
  1202.                 log.info("")
  1203.             
  1204.             elif format in ('para', 'name'):
  1205.                 log.info("")
  1206.                 log.info(text1)
  1207.                 log.info("")
  1208.             
  1209.             elif format in ('heading', 'header'):
  1210.                     
  1211.                 log.info("")
  1212.                 log.info("**" + text1 + "**")
  1213.                 log.info("")
  1214.                 log.info(".. class:: borderless")
  1215.                 log.info("")
  1216.                 log.info(''.join(["+", "-"*colwidth1, "+", "-"*colwidth2, "+"]))
  1217.                 
  1218.             elif format in ('option', 'example', 'seealso'):
  1219.                     
  1220.                 if text1 and '`_' not in text1:
  1221.                     log.info(''.join(["| *", text1, '*', " "*(colwidth1-len1-3), "|", text2, " "*(colwidth2-len2), "|"]))
  1222.                 elif text1:
  1223.                     log.info(''.join(["|", text1, " "*(colwidth1-len1), "|", text2, " "*(colwidth2-len2), "|"]))
  1224.                 else:
  1225.                     log.info(''.join(["|", " "*(colwidth1), "|", text2, " "*(colwidth2-len2), "|"]))
  1226.                 
  1227.                 log.info(''.join(["+", "-"*colwidth1, "+", "-"*colwidth2, "+"]))
  1228.                 
  1229.             elif format == 'note':
  1230.                 if text1.startswith(' '):
  1231.                     log.info(''.join(["|", " "*(tablewidth+1), "|"]))
  1232.                     
  1233.                 log.info(''.join(["|", text1, " "*(tablewidth-len1+1), "|"]))
  1234.                 log.info(''.join(["+", "-"*colwidth1, "+", "-"*colwidth2, "+"]))
  1235.             
  1236.             elif format == 'space':
  1237.                 log.info("")
  1238.         
  1239.         for l in links:
  1240.             log.info("\n.. _`%s`: %s.html\n" % (l, l.replace('hp-', '')))
  1241.         
  1242.         log.info("")
  1243.         
  1244.     elif typ == 'man':
  1245.         log.info('.TH "%s" 1 "%s" Linux "User Manuals"' % (title, version))
  1246.         
  1247.         for line in text_list:
  1248.             text1, text2, format, trailing_space = line
  1249.             
  1250.             text1 = text1.replace("\\*", "*")
  1251.             text2 = text2.replace("\\*", "*")            
  1252.             
  1253.             len1, len2 = len(text1), len(text2)
  1254.             
  1255.             if format == 'summary':
  1256.                 log.info(".SH SYNOPSIS")
  1257.                 log.info(".B %s" % text1)
  1258.                 
  1259.             elif format == 'name':
  1260.                 log.info(".SH NAME\n%s" % text1)
  1261.                 
  1262.             elif format in ('option', 'example', 'note'):
  1263.                 if text1:
  1264.                     log.info('.IP "%s"\n%s' % (text1, text2))
  1265.                 else:
  1266.                     log.info(text2)
  1267.                 
  1268.             elif format in ('header', 'heading'):
  1269.                 log.info(".SH %s" % text1.upper().replace(':', '').replace('[', '').replace(']', ''))
  1270.                 
  1271.             elif format in ('seealso, para'):
  1272.                 log.info(text1)
  1273.                 
  1274.         log.info("")
  1275.