utils.py 75.2 KB
Newer Older
1
#!/usr/bin/env python
2 3
# -*- coding: utf-8 -*-
#
4
# (c) Copyright 2001-2015 HP Development Company, L.P.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# 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
#
20
# Author: Don Welch, Naga Samrat Chowdary Narla, Goutam Kodu, Amarnath Chitumalla
21
#
22 23
# Thanks to Henrique M. Holschuh <hmh@debian.org> for various security patches
#
24

25

26 27

# Std Lib
28 29
import sys
import os
30
from subprocess import Popen, PIPE
31
import grp
32 33 34 35 36 37 38 39 40 41
import fnmatch
import tempfile
import socket
import struct
import select
import time
import fcntl
import errno
import stat
import string
42
import glob
43
import re
44 45
import datetime
from .g import *
46
import locale
47 48 49 50 51 52 53 54
from .sixext.moves import html_entities, urllib2_request, urllib2_parse, urllib2_error
from .sixext import PY3, to_unicode, to_bytes_utf8, to_string_utf8, BytesIO, StringIO, subprocess
from . import os_utils
try:
    import xml.parsers.expat as expat
    xml_expat_avail = True
except ImportError:
    xml_expat_avail = False
55

56 57 58 59 60
try:
    import platform
    platform_avail = True
except ImportError:
    platform_avail = False
61

62 63
try:
    import dbus
64
    from dbus import SystemBus, lowlevel, SessionBus
65 66 67 68
    dbus_avail=True
except ImportError:
    dbus_avail=False

69 70 71 72 73 74 75 76 77 78 79 80 81
try:
    import hashlib # new in 2.5

    def get_checksum(s):
        return hashlib.sha1(s).hexdigest()

except ImportError:
    import sha # deprecated in 2.6/3.0

    def get_checksum(s):
        return sha.new(s).hexdigest()


82 83


84
# Local
85 86 87 88
from .g import *
from .codes import *
from . import pexpect

89

90 91
BIG_ENDIAN = 0
LITTLE_ENDIAN = 1
92 93 94 95

RMDIR="rm -rf"
RM="rm -f"

96
DBUS_SERVICE='com.hplip.StatusService'
97

98
HPLIP_WEB_SITE ="http://hplipopensource.com/hplip-web/index.html"
99 100 101 102 103 104 105 106
HTTP_CHECK_TARGET = "http://www.hp.com"
PING_CHECK_TARGET = "www.hp.com"

ERROR_NONE = 0
ERROR_FILE_CHECKSUM = 1
ERROR_UNABLE_TO_RECV_KEYS =2 
ERROR_DIGITAL_SIGN_BAD =3

107 108
MAJ_VER = sys.version_info[0]
MIN_VER = sys.version_info[1]
109 110 111 112

EXPECT_WORD_LIST = [
    pexpect.EOF, # 0
    pexpect.TIMEOUT, # 1
113 114 115 116 117 118 119 120 121 122 123 124
    u"Continue?", # 2 (for zypper)
    u"passwor[dt]:", # en/de/it/ru
    u"kennwort", # de?
    u"password for", # en
    u"mot de passe", # fr
    u"contraseña", # es
    u"palavra passe", # pt
    u"口令", # zh
    u"wachtwoord", # nl
    u"heslo", # czech
    u"密码",
    u"Lösenord", #sv
125 126 127 128 129 130 131 132 133 134 135
]


EXPECT_LIST = []
for s in EXPECT_WORD_LIST:
    try:
        p = re.compile(s, re.I)
    except TypeError:
        EXPECT_LIST.append(s)
    else:
        EXPECT_LIST.append(p)
136

137 138

def get_cups_systemgroup_list():
139 140 141 142 143
    lis = []
    try:
        fp=open('/etc/cups/cupsd.conf')
    except IOError:
        try:
144 145
            if "root" != grp.getgrgid(os.stat('/etc/cups/cupsd.conf').st_gid).gr_name:
                return [grp.getgrgid(os.stat('/etc/cups/cupsd.conf').st_gid).gr_name]
146 147 148 149 150 151 152
        except OSError:
            return lis

    try:
        lis = ((re.findall('SystemGroup [\w* ]*',fp.read()))[0].replace('SystemGroup ','')).split(' ')
    except IndexError:
        return lis
153

154 155 156 157
    if 'root' in lis:
        lis.remove('root')
    fp.close()
    return lis
158 159

def lock(f):
160
    log.debug("Locking: %s" % f.name)
161 162 163
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        return True
164 165
    except (IOError, OSError):
        log.debug("Failed to unlock %s." % f.name)
166 167
        return False

168

169
def unlock(f):
170 171 172 173 174 175 176
    if f is not None:
        log.debug("Unlocking: %s" % f.name)
        try:
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)
            os.remove(f.name)
        except (IOError, OSError):
            pass
177 178


179
def lock_app(application, suppress_error=False):
180 181 182
    dir = prop.user_dir
    if os.geteuid() == 0:
        dir = '/var'
183

184 185 186 187
    elif not os.path.exists(dir):
        os.makedirs(dir)

    lock_file = os.path.join(dir, '.'.join([application, 'lock']))
188 189 190 191
    try:
        lock_file_f = open(lock_file, "w")
    except IOError:
        if not suppress_error:
192
            log.error("Unable to open %s lock file." % lock_file)
193 194
        return False, None

195
    #log.debug("Locking file: %s" % lock_file)
196

197 198 199 200
    if not lock(lock_file_f):
        if not suppress_error:
            log.error("Unable to lock %s. Is %s already running?" % (lock_file, application))
        return False, None
201

202 203
    return True, lock_file_f

204

205
#xml_basename_pat = re.compile(r"""HPLIP-(\d*)_(\d*)_(\d*).xml""", re.IGNORECASE)
206

207

208
def Translator(frm=to_bytes_utf8(''), to=to_bytes_utf8(''), delete=to_bytes_utf8(''), keep=None):  #Need Revisit
209 210
    if len(to) == 1:
        to = to * len(frm)
211 212 213 214 215 216 217 218

    if PY3:
        data_types = bytes
    else:
        data_types = string
    
    allchars = data_types.maketrans(to_bytes_utf8(''), to_bytes_utf8(''))
    trans = data_types.maketrans(frm, to)
219

220 221
    if keep is not None:
        delete = allchars.translate(allchars, keep.translate(allchars, delete))
222

223 224
    def callable(s):
        return s.translate(trans, delete)
225

226
    return callable
227 228


229 230 231 232 233 234 235 236
def list_to_string(lis):
    if len(lis) == 0:
        return ""
    if len(lis) == 1:
        return str("\""+lis[0]+"\"")
    if len(lis) >= 1:
        return "\""+"\", \"".join(lis)+"\" and \""+str(lis.pop())+"\""

237
def to_bool_str(s, default='0'):
238
    """ Convert an arbitrary 0/1/T/F/Y/N string to a normalized string 0/1."""
239
    if isinstance(s, str) and s:
240
        if s[0].lower() in ['1', 't', 'y']:
241
            return to_unicode('1')
242
        elif s[0].lower() in ['0', 'f', 'n']:
243
            return to_unicode('0')
244 245

    return default
246

247
def to_bool(s, default=False):
248
    """ Convert an arbitrary 0/1/T/F/Y/N string to a boolean True/False value."""
249
    if isinstance(s, str) and s:
250
        if s[0].lower() in ['1', 't', 'y']:
251
            return True
252
        elif s[0].lower() in ['0', 'f', 'n']:
253
            return False
254 255
    elif isinstance(s, bool):
        return s
256 257 258

    return default

259

260
# Compare with os.walk()
261
def walkFiles(root, recurse=True, abs_paths=False, return_folders=False, pattern='*', path=None):
262 263
    if path is None:
        path = root
264

265
    try:
266
        names = os.listdir(root)
267 268
    except os.error:
        raise StopIteration
269

270
    pattern = pattern or '*'
271
    pat_list = pattern.split(';')
272

273
    for name in names:
274
        fullname = os.path.normpath(os.path.join(root, name))
275

276
        for pat in pat_list:
277 278
            if fnmatch.fnmatch(name, pat):
                if return_folders or not os.path.isdir(fullname):
279 280 281 282
                    if abs_paths:
                        yield fullname
                    else:
                        try:
283
                            yield os.path.basename(fullname)
284 285
                        except ValueError:
                            yield fullname
286

287 288
        #if os.path.islink(fullname):
        #    fullname = os.path.realpath(os.readlink(fullname))
289

290
        if recurse and os.path.isdir(fullname): # or os.path.islink(fullname):
291
            for f in walkFiles(fullname, recurse, abs_paths, return_folders, pattern, path):
292 293 294
                yield f


295 296 297
def is_path_writable(path):
    if os.path.exists(path):
        s = os.stat(path)
298
        mode = s[stat.ST_MODE] & 0o777
299

300
        if mode & 0o2:
301
            return True
302
        elif s[stat.ST_GID] == os.getgid() and mode & 0o20:
303
            return True
304
        elif s[stat.ST_UID] == os.getuid() and mode & 0o200:
305
            return True
306

307
    return False
308

309 310 311 312 313

# Provides the TextFormatter class for formatting text into columns.
# Original Author: Hamish B Lawson, 1999
# Modified by: Don Welch, 2003
class TextFormatter:
314

315 316 317
    LEFT  = 0
    CENTER = 1
    RIGHT  = 2
318

319
    def __init__(self, colspeclist):
320 321
        self.columns = []
        for colspec in colspeclist:
322
            self.columns.append(Column(**colspec))
323 324 325 326 327

    def compose(self, textlist, add_newline=False):
        numlines = 0
        textlist = list(textlist)
        if len(textlist) != len(self.columns):
328
            log.error("Formatter: Number of text items does not match columns")
329
            return
330
        for text, column in list(map(lambda *x: x, textlist, self.columns)):
331 332 333 334 335 336 337 338 339 340
            column.wrap(text)
            numlines = max(numlines, len(column.lines))
        complines = [''] * numlines
        for ln in range(numlines):
            for column in self.columns:
                complines[ln] = complines[ln] + column.getline(ln)
        if add_newline:
            return '\n'.join(complines) + '\n'
        else:
            return '\n'.join(complines)
341

342 343 344
class Column:

    def __init__(self, width=78, alignment=TextFormatter.LEFT, margin=0):
345
        self.width = int(width)
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
        self.alignment = alignment
        self.margin = margin
        self.lines = []

    def align(self, line):
        if self.alignment == TextFormatter.CENTER:
            return line.center(self.width)
        elif self.alignment == TextFormatter.RIGHT:
            return line.rjust(self.width)
        else:
            return line.ljust(self.width)

    def wrap(self, text):
        self.lines = []
        words = []
        for word in text.split():
362
            if word <= str(self.width):
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
                words.append(word)
            else:
                for i in range(0, len(word), self.width):
                    words.append(word[i:i+self.width])
        if not len(words): return
        current = words.pop(0)
        for word in words:
            increment = 1 + len(word)
            if len(current) + increment > self.width:
                self.lines.append(self.align(current))
                current = word
            else:
                current = current + ' ' + word
        self.lines.append(self.align(current))

    def getline(self, index):
        if index < len(self.lines):
            return ' '*self.margin + self.lines[index]
        else:
            return ' ' * (self.margin + self.width)


385

386
class Stack:
387
    def __init__(self):
388
        self.stack = []
389

390
    def pop(self):
391
        return self.stack.pop()
392

393 394
    def push(self, value):
        self.stack.append(value)
395

396
    def as_list(self):
397
        return self.stack
398

399
    def clear(self):
400
        self.stack = []
401

402 403
    def __len__(self):
        return len(self.stack)
404 405 406



407 408 409 410 411 412
class Queue(Stack):
    def __init__(self):
        Stack.__init__(self)

    def get(self):
        return self.stack.pop(0)
413

414 415 416
    def put(self, value):
        Stack.push(self, value)

417

418

419
# RingBuffer class
420 421 422 423
# Source: Python Cookbook 1st Ed., sec. 5.18, pg. 201
# Credit: Sebastien Keim
# License: Modified BSD
class RingBuffer:
424
    def __init__(self, size_max=50):
425 426
        self.max = size_max
        self.data = []
427

428 429 430
    def append(self,x):
        """append an element at the end of the buffer"""
        self.data.append(x)
431

432
        if len(self.data) == self.max:
433
            self.cur = 0
434
            self.__class__ = RingBufferFull
435

436 437 438
    def replace(self, x):
        """replace the last element instead off appending"""
        self.data[-1] = x
439

440 441 442 443 444 445
    def get(self):
        """ return a list of elements from the oldest to the newest"""
        return self.data


class RingBufferFull:
446
    def __init__(self, n):
447 448
        #raise "you should use RingBuffer"
        pass
449

450
    def append(self, x):
451 452
        self.data[self.cur] = x
        self.cur = (self.cur+1) % self.max
453

454 455 456 457 458 459
    def replace(self, x):
        # back up 1 position to previous location
        self.cur = (self.cur-1) % self.max
        self.data[self.cur] = x
        # setup for next item
        self.cur = (self.cur+1) % self.max
460

461
    def get(self):
462
        return self.data[self.cur:] + self.data[:self.cur]
463

464 465


466 467
def sort_dict_by_value(d):
    """ Returns the keys of dictionary d sorted by their values """
468
    items=list(d.items())
469
    backitems=[[v[1],v[0]] for v in items]
470
    backitems.sort()
471
    return [backitems[i][1] for i in range(0, len(backitems))]
472

473 474

def commafy(val):
475
    return locale.format("%s", val, grouping=True)
476

477

478
def format_bytes(s, show_bytes=False):
479
    if s < 1024:
480
        return ''.join([commafy(s), ' B'])
481 482
    elif 1024 < s < 1048576:
        if show_bytes:
483
            return ''.join([to_unicode(round(s/1024.0, 1)) , to_unicode(' KB ('),  commafy(s), ')'])
484
        else:
485
            return ''.join([to_unicode(round(s/1024.0, 1)) , to_unicode(' KB')])
486 487
    elif 1048576 < s < 1073741824:
        if show_bytes:
488
            return ''.join([to_unicode(round(s/1048576.0, 1)), to_unicode(' MB ('),  commafy(s), ')'])
489
        else:
490
            return ''.join([to_unicode(round(s/1048576.0, 1)), to_unicode(' MB')])
491 492
    else:
        if show_bytes:
493
            return ''.join([to_unicode(round(s/1073741824.0, 1)), to_unicode(' GB ('),  commafy(s), ')'])
494
        else:
495
            return ''.join([to_unicode(round(s/1073741824.0, 1)), to_unicode(' GB')])
496

497 498 499 500


try:
    make_temp_file = tempfile.mkstemp # 2.3+
501
except AttributeError:
502 503
    def make_temp_file(suffix='', prefix='', dir='', text=False): # pre-2.3
        path = tempfile.mktemp(suffix)
504
        fd = os.open(path, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0o700)
505 506
        return ( os.fdopen( fd, 'w+b' ), path )

507

508

509
def which(command, return_full_path=False):
510 511 512 513
    path=[]
    path_val = os.getenv('PATH')
    if path_val:
        path = path_val.split(':')
514

515 516
    path.append('/usr/bin')
    path.append('/usr/local/bin')
517 518 519 520 521
    # Add these paths for Fedora
    path.append('/sbin')
    path.append('/usr/sbin')
    path.append('/usr/local/sbin')

522 523 524
    found_path = ''
    for p in path:
        try:
525
            files = os.listdir(p)
526
        except OSError:
527 528 529 530 531
            continue
        else:
            if command in files:
                found_path = p
                break
532

533 534 535 536 537 538 539
    if return_full_path:
        if found_path:
            return os.path.join(found_path, command)
        else:
            return ''
    else:
        return found_path
540 541


542
class UserSettings(object): # Note: Deprecated after 2.8.8 in Qt4 (see ui4/ui_utils.py)
543 544
    def __init__(self):
        self.load()
545

546 547 548 549
    def loadDefaults(self):
        # Print
        self.cmd_print = ''
        path = which('hp-print')
550

551
        if len(path) > 0:
552
            self.cmd_print = 'hp-print -p%PRINTER%'
553
        else:
554
            path = which('kprinter')
555
            if len(path) > 0:
556
                self.cmd_print = 'kprinter -P%PRINTER% --system cups'
557
            else:
558
                path = which('gtklp')
559
                if len(path) > 0:
560 561 562 563 564
                    self.cmd_print = 'gtklp -P%PRINTER%'
                else:
                    path = which('xpp')
                    if len(path) > 0:
                        self.cmd_print = 'xpp -P%PRINTER%'
565

566 567 568
        # Scan
        self.cmd_scan = ''
        path = which('xsane')
569

570 571
        if len(path) > 0:
            self.cmd_scan = 'xsane -V %SANE_URI%'
572
        else:
573 574 575 576 577 578 579
            path = which('kooka')
            if len(path) > 0:
                self.cmd_scan = 'kooka'
            else:
                path = which('xscanimage')
                if len(path) > 0:
                    self.cmd_scan = 'xscanimage'
580

581 582
        # Photo Card
        path = which('hp-unload')
583

584 585 586 587
        if len(path):
            self.cmd_pcard = 'hp-unload -d %DEVICE_URI%'
        else:
            self.cmd_pcard = 'python %HOME%/unload.py -d %DEVICE_URI%'
588

589 590
        # Copy
        path = which('hp-makecopies')
591

592 593 594 595
        if len(path):
            self.cmd_copy = 'hp-makecopies -d %DEVICE_URI%'
        else:
            self.cmd_copy = 'python %HOME%/makecopies.py -d %DEVICE_URI%'
596

597 598
        # Fax
        path = which('hp-sendfax')
599

600 601 602 603
        if len(path):
            self.cmd_fax = 'hp-sendfax -d %FAX_URI%'
        else:
            self.cmd_fax = 'python %HOME%/sendfax.py -d %FAX_URI%'
604

605 606
        # Fax Address Book
        path = which('hp-fab')
607

608 609 610
        if len(path):
            self.cmd_fab = 'hp-fab'
        else:
611
            self.cmd_fab = 'python %HOME%/fab.py'
612

613 614 615
    def load(self):
        self.loadDefaults()
        log.debug("Loading user settings...")
616
        self.auto_refresh = to_bool(user_conf.get('refresh', 'enable', '0'))
617

618
        try:
619
            self.auto_refresh_rate = int(user_conf.get('refresh', 'rate', '30'))
620
        except ValueError:
621
            self.auto_refresh_rate = 30 # (secs)
622

623
        try:
624
            self.auto_refresh_type = int(user_conf.get('refresh', 'type', '0'))
625 626
        except ValueError:
            self.auto_refresh_type = 0 # refresh 1 (1=refresh all)
627

628 629 630 631 632 633
        self.cmd_print = user_conf.get('commands', 'prnt', self.cmd_print)
        self.cmd_scan = user_conf.get('commands', 'scan', self.cmd_scan)
        self.cmd_pcard = user_conf.get('commands', 'pcard', self.cmd_pcard)
        self.cmd_copy = user_conf.get('commands', 'cpy', self.cmd_copy)
        self.cmd_fax = user_conf.get('commands', 'fax', self.cmd_fax)
        self.cmd_fab = user_conf.get('commands', 'fab', self.cmd_fab)
634 635

        self.upgrade_notify= to_bool(user_conf.get('upgrade', 'notify_upgrade', '0'))
636
        self.upgrade_last_update_time = int(user_conf.get('upgrade','last_upgraded_time', '0'))
637 638
        self.upgrade_pending_update_time =int(user_conf.get('upgrade', 'pending_upgrade_time', '0'))
        self.latest_available_version=str(user_conf.get('upgrade', 'latest_available_version',''))
639
        self.debug()
640

641 642 643 644 645 646 647 648 649
    def debug(self):
        log.debug("Print command: %s" % self.cmd_print)
        log.debug("PCard command: %s" % self.cmd_pcard)
        log.debug("Fax command: %s" % self.cmd_fax)
        log.debug("FAB command: %s" % self.cmd_fab)
        log.debug("Copy command: %s " % self.cmd_copy)
        log.debug("Scan command: %s" % self.cmd_scan)
        log.debug("Auto refresh: %s" % self.auto_refresh)
        log.debug("Auto refresh rate: %s" % self.auto_refresh_rate)
650
        log.debug("Auto refresh type: %s" % self.auto_refresh_type)
651 652 653 654
        log.debug("Upgrade notification:%d"  %self.upgrade_notify)
        log.debug("Last Installed time:%d" %self.upgrade_last_update_time)
        log.debug("Next scheduled installation time:%d" % self.upgrade_pending_update_time)

655

656 657
    def save(self):
        log.debug("Saving user settings...")
658 659 660 661 662 663 664 665
        user_conf.set('commands', 'prnt', self.cmd_print)
        user_conf.set('commands', 'pcard', self.cmd_pcard)
        user_conf.set('commands', 'fax', self.cmd_fax)
        user_conf.set('commands', 'scan', self.cmd_scan)
        user_conf.set('commands', 'cpy', self.cmd_copy)
        user_conf.set('refresh', 'enable',self.auto_refresh)
        user_conf.set('refresh', 'rate', self.auto_refresh_rate)
        user_conf.set('refresh', 'type', self.auto_refresh_type)
666 667 668 669 670
        user_conf.set('upgrade', 'notify_upgrade', self.upgrade_notify)
        user_conf.set('upgrade','last_upgraded_time', self.upgrade_last_update_time)
        user_conf.set('upgrade', 'pending_upgrade_time', self.upgrade_pending_update_time)
        user_conf.set('upgrade', 'latest_available_version', self.latest_available_version)

671
        self.debug()
672

673 674


675 676 677 678 679
def no_qt_message_gtk():
    try:
        import gtk
        w = gtk.Window()
        dialog = gtk.MessageDialog(w, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
680
                                   gtk.MESSAGE_WARNING, gtk.BUTTONS_OK,
681 682 683
                                   "PyQt not installed. GUI not available. Please check that the PyQt package is installed. Exiting.")
        dialog.run()
        dialog.destroy()
684

685
    except ImportError:
686 687 688
        log.error("PyQt not installed. GUI not available. Please check that the PyQt package is installed. Exiting.")


689
def canEnterGUIMode(): # qt3
690 691 692 693 694 695 696 697 698
    if not prop.gui_build:
        log.warn("GUI mode disabled in build.")
        return False

    elif not os.getenv('DISPLAY'):
        log.warn("No display found.")
        return False

    elif not checkPyQtImport():
699
        log.warn("Qt/PyQt 3 initialization failed.")
700 701 702
        return False

    return True
703

704

705 706 707 708 709 710 711 712 713
def canEnterGUIMode4(): # qt4
    if not prop.gui_build:
        log.warn("GUI mode disabled in build.")
        return False

    elif not os.getenv('DISPLAY'):
        log.warn("No display found.")
        return False

714 715 716 717 718 719 720 721 722
    # elif not checkPyQtImport4():
    #     log.warn("Qt4/PyQt 4 initialization failed.")
    #     return False
    else:
        try:
            checkPyQtImport45()
        except ImportError as e:
            log.warn(e)
            return False
723 724 725

    return True

726

727
def checkPyQtImport(): # qt3
728 729 730
    # PyQt
    try:
        import qt
731
        import ui
732
    except ImportError:
733 734 735
        if os.getenv('DISPLAY') and os.getenv('STARTED_FROM_MENU'):
            no_qt_message_gtk()

736
        log.error("PyQt not installed. GUI not available. Exiting.")
737
        return False
738

739
    # check version of Qt
740
    qtMajor = int(qt.qVersion().split('.')[0])
741 742 743

    if qtMajor < MINIMUM_QT_MAJOR_VER:

744
        log.error("Incorrect version of Qt installed. Ver. 3.0.0 or greater required.")
745
        return False
746

747 748 749
    #check version of PyQt
    try:
        pyqtVersion = qt.PYQT_VERSION_STR
750
    except AttributeError:
751
        pyqtVersion = qt.PYQT_VERSION
752

753 754
    while pyqtVersion.count('.') < 2:
        pyqtVersion += '.0'
755

756
    (maj_ver, min_ver, pat_ver) = pyqtVersion.split('.')
757

758 759
    if pyqtVersion.find('snapshot') >= 0:
        log.warning("A non-stable snapshot version of PyQt is installed.")
760
    else:
761 762 763 764 765 766
        try:
            maj_ver = int(maj_ver)
            min_ver = int(min_ver)
            pat_ver = int(pat_ver)
        except ValueError:
            maj_ver, min_ver, pat_ver = 0, 0, 0
767

768 769
        if maj_ver < MINIMUM_PYQT_MAJOR_VER or \
            (maj_ver == MINIMUM_PYQT_MAJOR_VER and min_ver < MINIMUM_PYQT_MINOR_VER):
770 771
            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))
            log.error("Incorrect version of pyQt installed. Ver. %d.%d or greater required." % (MINIMUM_PYQT_MAJOR_VER, MINIMUM_PYQT_MINOR_VER))
772 773
            log.error("This program will continue, but you may experience errors, crashes or other problems.")
            return True
774

775
    return True
776

777

778 779 780
def checkPyQtImport4():
    try:
        import PyQt4
781
        import ui4
782
    except ImportError:
783 784
        import PyQt5
        import ui5
785
    else:
786 787 788 789 790 791 792 793 794 795 796 797 798
        log.debug("HPLIP is not installed properly or is installed without graphical support. Please reinstall HPLIP again")
        return False
    return True

# def checkPyQtImport5():
#     try:
#         import PyQt5
#         import ui5
#     except ImportError:
#         log.error("HPLIP is not installed properly or is installed without graphical support PyQt5. Please reinstall HPLIP")
#         return False
#     else:
#         return True
799

800

801 802 803 804
try:
    from string import Template # will fail in Python <= 2.3
except ImportError:
    # Code from Python 2.4 string.py
805
    #import re as _re
806

807 808
    class _multimap:
        """Helper class for combining multiple mappings.
809

810 811 812 813 814 815
        Used by .{safe_,}substitute() to combine the mapping and keyword
        arguments.
        """
        def __init__(self, primary, secondary):
            self._primary = primary
            self._secondary = secondary
816

817 818 819 820 821
        def __getitem__(self, key):
            try:
                return self._primary[key]
            except KeyError:
                return self._secondary[key]
822 823


824 825 826 827 828 829 830 831 832
    class _TemplateMetaclass(type):
        pattern = r"""
        %(delim)s(?:
          (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
          (?P<named>%(id)s)      |   # delimiter and a Python identifier
          {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
          (?P<invalid>)              # Other ill-formed delimiter exprs
        )
        """
833

834 835 836 837 838 839
        def __init__(cls, name, bases, dct):
            super(_TemplateMetaclass, cls).__init__(name, bases, dct)
            if 'pattern' in dct:
                pattern = cls.pattern
            else:
                pattern = _TemplateMetaclass.pattern % {
840
                    'delim' : re.escape(cls.delimiter),
841 842
                    'id'    : cls.idpattern,
                    }
843
            cls.pattern = re.compile(pattern, re.IGNORECASE | re.VERBOSE)
844

845 846 847 848
    # if PY3:
    #     class Template(metaclass=_TemplateMetaclass):
    #         """A string class for supporting $-substitutions."""
    # else:
849 850 851
    class Template:
        """A string class for supporting $-substitutions."""
        __metaclass__ = _TemplateMetaclass
852

853 854
        delimiter = '$'
        idpattern = r'[_a-z][_a-z0-9]*'
855

856 857
        def __init__(self, template):
            self.template = template
858

859 860 861 862 863 864 865 866 867 868 869 870
        # Search for $$, $identifier, ${identifier}, and any bare $'s
        def _invalid(self, mo):
            i = mo.start('invalid')
            lines = self.template[:i].splitlines(True)
            if not lines:
                colno = 1
                lineno = 1
            else:
                colno = i - len(''.join(lines[:-1]))
                lineno = len(lines)
            raise ValueError('Invalid placeholder in string: line %d, col %d' %
                             (lineno, colno))
871

872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896
        def substitute(self, *args, **kws):
            if len(args) > 1:
                raise TypeError('Too many positional arguments')
            if not args:
                mapping = kws
            elif kws:
                mapping = _multimap(kws, args[0])
            else:
                mapping = args[0]
            # Helper function for .sub()
            def convert(mo):
                # Check the most common path first.
                named = mo.group('named') or mo.group('braced')
                if named is not None:
                    val = mapping[named]
                    # We use this idiom instead of str() because the latter will
                    # fail if val is a Unicode containing non-ASCII characters.
                    return '%s' % val
                if mo.group('escaped') is not None:
                    return self.delimiter
                if mo.group('invalid') is not None:
                    self._invalid(mo)
                raise ValueError('Unrecognized named group in pattern',
                                 self.pattern)
            return self.pattern.sub(convert, self.template)
897

898

899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
        def safe_substitute(self, *args, **kws):
            if len(args) > 1:
                raise TypeError('Too many positional arguments')
            if not args:
                mapping = kws
            elif kws:
                mapping = _multimap(kws, args[0])
            else:
                mapping = args[0]
            # Helper function for .sub()
            def convert(mo):
                named = mo.group('named')
                if named is not None:
                    try:
                        # We use this idiom instead of str() because the latter
                        # will fail if val is a Unicode containing non-ASCII
                        return '%s' % mapping[named]
                    except KeyError:
                        return self.delimiter + named
                braced = mo.group('braced')
                if braced is not None:
                    try:
                        return '%s' % mapping[braced]
                    except KeyError:
                        return self.delimiter + '{' + braced + '}'
                if mo.group('escaped') is not None:
                    return self.delimiter
                if mo.group('invalid') is not None:
                    return self.delimiter
                raise ValueError('Unrecognized named group in pattern',
                                 self.pattern)
            return self.pattern.sub(convert, self.template)
931

932 933


934 935 936 937 938 939
#cat = lambda _ : Template(_).substitute(sys._getframe(1).f_globals, **sys._getframe(1).f_locals)

def cat(s):
    globals = sys._getframe(1).f_globals.copy()
    if 'self' in globals:
        del globals['self']
940

941 942 943
    locals = sys._getframe(1).f_locals.copy()
    if 'self' in locals:
        del locals['self']
944

945
    return Template(s).substitute(sys._getframe(1).f_globals, **locals)
946

947 948 949 950 951 952
if PY3:
    identity = bytes.maketrans(b'', b'')
    unprintable = identity.translate(identity, string.printable.encode('utf-8'))
else:
    identity = string.maketrans('','')
    unprintable = identity.translate(identity, string.printable)
953

954

955
def printable(s):
956 957 958 959
    if s:
        return s.translate(identity, unprintable)
    else:
        return ""
960 961


962 963 964 965 966
def any(S,f=lambda x:x):
    for x in S:
        if f(x): return True
    return False

967

968 969 970 971
def all(S,f=lambda x:x):
    for x in S:
        if not f(x): return False
    return True
972

973 974
BROWSERS = ['firefox', 'mozilla', 'konqueror', 'epiphany', 'skipstone'] # in preferred order
BROWSER_OPTS = {'firefox': '-new-tab', 'mozilla': '', 'konqueror': '', 'epiphany': '--new-tab', 'skipstone': ''}
975

976

977
def find_browser():
978
    if platform_avail and platform.system() == 'Darwin':
979
        return "open"
980 981
    elif which("xdg-open"):
        return "xdg-open"
982 983 984 985 986 987
    else:
        for b in BROWSERS:
            if which(b):
                return b
        else:
            return None
988

989 990

def openURL(url, use_browser_opts=True):
991
    if platform_avail and platform.system() == 'Darwin':
992
        cmd = 'open "%s"' % url
993
        os_utils.execute(cmd)
994 995
    elif which("xdg-open"):
        cmd = 'xdg-open "%s"' % url
996
        os_utils.execute(cmd)
997
    else:
998
        for b in BROWSERS:
999
            bb = which(b, return_full_path='True')
1000
            if bb:
1001 1002 1003 1004
                if use_browser_opts:
                    cmd = """%s %s "%s" &""" % (bb, BROWSER_OPTS[b], url)
                else:
                    cmd = """%s "%s" &""" % (bb, url)
1005
                os_utils.execute(cmd)
1006 1007 1008
                break
        else:
            log.warn("Unable to open URL: %s" % url)
1009 1010


1011 1012 1013 1014
def uniqueList(input):
    temp = []
    [temp.append(i) for i in input if not temp.count(i)]
    return temp
1015

1016

1017 1018 1019 1020 1021
def list_move_up(l, m, cmp=None):
    if cmp is None:
        f = lambda x: l[x] == m
    else:
        f = lambda x: cmp(l[x], m)
1022

1023
    for i in range(1, len(l)):
1024
        if f(i):
1025 1026
            l[i-1], l[i] = l[i], l[i-1]

1027

1028 1029 1030 1031 1032
def list_move_down(l, m, cmp=None):
    if cmp is None:
        f = lambda x: l[x] == m
    else:
        f = lambda x: cmp(l[x], m)
1033

1034
    for i in range(len(l)-2, -1, -1):
1035
        if f(i):
1036 1037
            l[i], l[i+1] = l[i+1], l[i]

1038

1039 1040 1041 1042 1043

class XMLToDictParser:
    def __init__(self):
        self.stack = []
        self.data = {}
1044
        self.last_start = ''
1045

1046
    def startElement(self, name, attrs):
1047
        #print "START:", name, attrs
1048 1049
        self.stack.append(to_unicode(name).lower())
        self.last_start = to_unicode(name).lower()
1050

1051 1052
        if len(attrs):
            for a in attrs:
1053
                self.stack.append(to_unicode(a).lower())
1054 1055
                self.addData(attrs[a])
                self.stack.pop()
1056

1057
    def endElement(self, name):
1058 1059
        if name.lower() == self.last_start:
            self.addData('')
1060

1061
        #print "END:", name
1062
        self.stack.pop()
1063

1064
    def charData(self, data):
1065
        data = to_unicode(data).strip()
1066

1067 1068
        if data and self.stack:
            self.addData(data)
1069

1070
    def addData(self, data):
1071
        #print("DATA:%s" % data)
1072
        self.last_start = ''
1073 1074 1075
        try:
            data = int(data)
        except ValueError:
1076
            data = to_unicode(data)
1077

1078 1079
        stack_str = '-'.join(self.stack)
        stack_str_0 = '-'.join([stack_str, '0'])
1080

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
        try:
            self.data[stack_str]
        except KeyError:
            try:
                self.data[stack_str_0]
            except KeyError:
                self.data[stack_str] = data
            else:
                j = 2
                while True:
                    try:
1092
                        self.data['-'.join([stack_str, to_unicode(j)])]
1093
                    except KeyError:
1094
                        self.data['-'.join([stack_str, to_unicode(j)])] = data
1095
                        break
1096
                    j += 1
1097

1098 1099 1100 1101
        else:
            self.data[stack_str_0] = self.data[stack_str]
            self.data['-'.join([stack_str, '1'])] = data
            del self.data[stack_str]
1102

1103

1104
    def parseXML(self, text):
1105 1106
        if xml_expat_avail:
            parser = expat.ParserCreate()
1107

1108 1109 1110
            parser.StartElementHandler = self.startElement
            parser.EndElementHandler = self.endElement
            parser.CharacterDataHandler = self.charData
1111

1112 1113 1114 1115 1116 1117
            parser.Parse(text, True)

        else:
            log.error("Failed to import expat module , check python-xml/python3-xml package installation.")

        return self.data
1118

1119 1120 1121 1122 1123 1124 1125

class Element:
    def __init__(self,name,attributes):
        self.name = name
        self.attributes = attributes
        self.chardata = ''
        self.children = []
1126

1127 1128
    def AddChild(self,element):
        self.children.append(element)
1129

1130 1131
    def getAttribute(self,key):
        return self.attributes.get(key)
1132

1133 1134
    def getData(self):
        return self.chardata
1135

1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
    def getElementsByTagName(self,name='',ElementNode=None):
        if ElementNode:
            Children_list = ElementNode.children
        else:
            Children_list = self.children
        if not name:
            return self.children
        else:
            elements = []
            for element in Children_list:
                if element.name == name:
                    elements.append(element)
1148

1149 1150 1151 1152
                rec_elements = self.getElementsByTagName (name,element)
                for a in rec_elements:
                    elements.append(a)
            return elements
1153

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
    def getChildElements(self,name=''):
        if not name:
            return self.children
        else:
            elements = []
            for element in self.children:
                if element.name == name:
                    elements.append(element)
            return elements

    def toString(self, level=0):
        retval = " " * level
        retval += "<%s" % self.name
        for attribute in self.attributes:
            retval += " %s=\"%s\"" % (attribute, self.attributes[attribute])
        c = ""
        for child in self.children:
            c += child.toString(level+1)
        if c == "":
            if self.chardata:
1174
                retval += ">"+self.chardata + ("</%s>" % self.name)
1175 1176 1177 1178 1179
            else:
                retval += "/>"
        else:
            retval += ">" + c + ("</%s>" % self.name)
        return retval
1180

1181 1182 1183 1184
class  extendedExpat:
    def __init__(self):
        self.root = None
        self.nodeStack = []
1185

1186
    def StartElement_EE(self,name,attributes):
1187
        element = Element(name, attributes)
1188

1189 1190 1191 1192 1193 1194
        if len(self.nodeStack) > 0:
            parent = self.nodeStack[-1]
            parent.AddChild(element)
        else:
            self.root = element
        self.nodeStack.append(element)
1195

1196 1197 1198
    def EndElement_EE(self,name):
        self.nodeStack = self.nodeStack[:-1]

1199
    def charData_EE(self,data):
1200
        if data:
1201 1202 1203 1204 1205
            element = self.nodeStack[-1]
            element.chardata += data
            return

    def Parse(self,xmlString):
1206 1207
        if xml_expat_avail:
            Parser = expat.ParserCreate()
1208

1209 1210 1211
            Parser.StartElementHandler = self.StartElement_EE
            Parser.EndElementHandler = self.EndElement_EE
            Parser.CharacterDataHandler = self.charData_EE
1212

1213 1214 1215
            Parser.Parse(xmlString, True)
        else:
            log.error("Failed to import expat module , check python-xml/python3-xml package installation.")
1216

1217 1218 1219 1220
        return self.root



1221 1222
def dquote(s):
    return ''.join(['"', s, '"'])
1223

1224

1225 1226 1227 1228
# Python 2.2.x compatibility functions (strip() family with char argument added in Python 2.2.3)
if sys.hexversion < 0x020203f0:
    def xlstrip(s, chars=' '):
        i = 0
1229
        for c, i in zip(s, list(range(len(s)))):
1230 1231 1232 1233
            if c not in chars:
                break

        return s[i:]
1234

1235 1236
    def xrstrip(s,