#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Author: Don Welch
#

__version__ = '13.0'
__title__ = 'Dependency/Version Check Utility'
__doc__ = "Check the existence and versions of HPLIP dependencies."

# Std Lib
import sys
import os
import getopt
import commands
import re

# Local
from base.g import *
from base import utils, tui, models
from installer import dcheck
from installer.core_install import *

device_avail = False
try:
    from base import device, pml
except ImportError:
    log.debug("Device library is not avail.")
else:
    device_avail = True


USAGE = [(__doc__, "", "name", True),
         ("Usage: hp-check/check.py [OPTIONS]", "", "summary", True),
         utils.USAGE_OPTIONS,
         #("Pre-install check:", "-p or --pre", "option", False),
         ("Compile-time check:", "-c or --compile", "option", False),
         ("Run-time check:", "-r or --run", "option", False),
         ("Compile and run-time checks:", "-b or --both (default)", "option", False),
         utils.USAGE_LOGGING1, utils.USAGE_LOGGING2, utils.USAGE_LOGGING3,
         utils.USAGE_LOGGING_PLAIN,
         utils.USAGE_HELP,
         utils.USAGE_NOTES,
         #("1. For posting to the mailing list, use the -t parameter and then copy/paste the onscreen output or use the generated hp-check.log file.", "", "note", False),
         #("2. Use with the '-p' switch prior to installation to check for dependencies and system requirements (skips some checks).", "", "note", False),
         #("3. Run without the '-p' switch after installation to check for proper install (runs all checks). ", "", "note", False),
         ("1. For checking for the proper build environment for the HPLIP supplied tarball (.tar.gz or .run),", "", "note", False), 
         ("use the --compile or --both switches.", "", "note", False),
         ("2. For checking for the proper runtime environment for a distro supplied package (.deb, .rpm, etc),", "", "note", False), 
         ("use the --runtime switch.", "", "note", False),
        ]

def usage(typ='text'):
    if typ == 'text':
        utils.log_title(__title__, __version__)

    utils.format_text(USAGE, typ, __title__, 'hp-check', __version__)
    sys.exit(0)        

build_str = "HPLIP will not build, install, and/or function properly without this dependency."

pat_deviceuri = re.compile(r"""(.*):/(.*?)/(\S*?)\?(?:serial=(\S*)|device=(\S*)|ip=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}[^&]*))(?:&port=(\d))?""", re.IGNORECASE)

def parseDeviceURI(device_uri):
    m = pat_deviceuri.match(device_uri)

    if m is None:
        raise Error(ERROR_INVALID_DEVICE_URI)

    back_end = m.group(1).lower() or ''
    is_hp = (back_end in ('hp', 'hpfax', 'hpaio'))
    bus = m.group(2).lower() or ''

    if bus not in ('usb', 'net', 'bt', 'fw', 'par'):
        raise Error(ERROR_INVALID_DEVICE_URI)

    model = m.group(3) or ''
    serial = m.group(4) or ''
    dev_file = m.group(5) or ''
    host = m.group(6) or ''
    port = m.group(7) or 1

    if bus == 'net':
        try:
            port = int(port)
        except (ValueError, TypeError):
            port = 1

        if port == 0:
            port = 1

    return back_end, is_hp, bus, model, serial, dev_file, host, port

num_errors = 0
fmt = True
overall_commands_to_run = []
time_flag = DEPENDENCY_RUN_AND_COMPILE_TIME

try:
    log.set_module("hp-check")

    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hl:gtcrb', 
            ['help', 'help-rest', 'help-man', 'help-desc', 'logging=', 
             'run', 'compile', 'both']) 

    except getopt.GetoptError, e:
        log.error(e.msg)
        usage()
        sys.exit(1)

    if os.getenv("HPLIP_DEBUG"):
        log.set_level('debug')

    log_level = 'info'

    for o, a in opts:
        if o in ('-h', '--help'):
            usage()

        elif o == '--help-rest':
            usage('rest')

        elif o == '--help-man':
            usage('man')

        elif o == '--help-desc':
            print __doc__,
            sys.exit(0)

        elif o in ('-l', '--logging'):
            log_level = a.lower().strip()

        elif o == '-g':
            log_level = 'debug'

        elif o == '-t':
            fmt = False
            
        elif o in ('-c', '--compile'):
            time_flag = DEPENDENCY_COMPILE_TIME
            
        elif o in ('-r', '--runtime'):
            time_flag = DEPENDENCY_RUN_TIME
            
        elif o in ('-b', '--both'):
            time_flag = DEPENDENCY_RUN_AND_COMPILE_TIME

    if not log.set_level(log_level):
        usage()

    if not fmt:
        log.no_formatting()

    utils.log_title(__title__, __version__)

    log.info(log.bold("Note: hp-check can be run in three modes:"))
    
    for l in tui.format_paragraph("1. Compile-time check mode (-c or --compile): Use this mode before compiling the HPLIP supplied tarball (.tar.gz or .run) to determine if the proper dependencies are installed to successfully compile HPLIP."):
        log.info(l)
        
    for l in tui.format_paragraph("2. Run-time check mode (-r or --run): Use this mode to determine if a distro supplied package (.deb, .rpm, etc) or an already built HPLIP supplied tarball has the proper dependencies installed to successfully run."):
        log.info(l)
    
    for l in tui.format_paragraph("3. Both compile- and run-time check mode (-b or --both) (Default): This mode will check both of the above cases (both compile- and run-time dependencies)."):
        log.info(l)
    
    log.info("")
    
    
    log_file = os.path.normpath('./hp-check.log')
    log.info(log.bold("Saving output in log file: %s" % log_file))
    log.debug("Log file=%s" % log_file)
    if os.path.exists(log_file):
        os.remove(log_file)

    log.set_logfile(log_file)
    log.set_where(log.LOG_TO_CONSOLE_AND_FILE)

    log.info("\nInitializing. Please wait...")
    core =  CoreInstall(MODE_CHECK)
    core.init()

    tui.header("SYSTEM INFO")

    log.info(log.bold("Basic system information:"))
    log.info(core.sys_uname_info)

    log.info("")
    log.info(log.bold("Distribution:"))
    log.info("%s %s" % (core.distro_name, core.distro_version))

    log.info(log.bold("\nHPOJ running?"))

    if core.hpoj_present:
        log.error("Yes, HPOJ is running. HPLIP is not compatible with HPOJ. To run HPLIP, please remove HPOJ.")
        num_errors += 1
    else:
        log.info("No, HPOJ is not running (OK).")


    log.info("")
    log.info(log.bold("Checking Python version..."))
    ver = sys.version_info
    log.debug("sys.version_info = %s" % repr(ver))
    ver_maj = ver[0]
    ver_min = ver[1]
    ver_pat = ver[2]

    if ver_maj == 2:
        if ver_min >= 1:
            log.info("OK, version %d.%d.%d installed" % ver[:3])
        else:
            log.error("Version %d.%d.%d installed. Please update to Python >= 2.1" % ver[:3])
            sys.exit(1)


    log.info("")
    log.info(log.bold("Checking PyQt version..."))

    # PyQt
    try:
        import qt
    except ImportError:
        num_errors += 1
        log.error("NOT FOUND OR FAILED TO LOAD!")
    else:
        # check version of Qt
        qtMajor = int(qt.qVersion().split('.')[0])

        if qtMajor < MINIMUM_QT_MAJOR_VER:
            log.error("Incorrect version of Qt installed. Ver. 3.0.0 or greater required.")
        else:
            #check version of PyQt
            try:
                pyqtVersion = qt.PYQT_VERSION_STR
            except:
                pyqtVersion = qt.PYQT_VERSION

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

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

            if pyqtVersion.find('snapshot') >= 0:
                log.error("A non-stable snapshot version of PyQt is installed (%s)." % pyqtVersion)
                num_errors += 1
            else:
                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

                if maj_ver < MINIMUM_PYQT_MAJOR_VER or \
                    (maj_ver == MINIMUM_PYQT_MAJOR_VER and min_ver < MINIMUM_PYQT_MINOR_VER):
                    num_errors += 1
                    log.error("HPLIP may not function properly with the version of PyQt that is installed (%d.%d.%d)." % (maj_ver, min_ver, pat_ver))
                    log.error("Ver. %d.%d or greater required." % (MINIMUM_PYQT_MAJOR_VER, MINIMUM_PYQT_MINOR_VER))
                else:
                    log.info("OK, version %d.%d installed." % (maj_ver, min_ver))


    log.info("")
    log.info(log.bold("Checking SIP version..."))

    sip_ver = None
    try:
        import pyqtconfig
    except ImportError:
        pass
    else:
        sip_ver = pyqtconfig.Configuration().sip_version_str 

    if sip_ver is not None:
        log.info("OK, Version %s installed" % sip_ver)
    else:
        num_errors += 1
        log.error("SIP not installed or version not found.")

    log.info("")
    log.info(log.bold("Checking for CUPS..."))

    status, output = utils.run('lpstat -r')
    if status == 0:
        log.info("Status: %s" % output.strip())
    else:
        log.error("Status: (Not available. CUPS may not be installed or not running.)")
        num_errors += 1

    status, output = utils.run('cups-config --version')
    if status == 0:
        log.info("Version: %s" % output.strip())
    else:
        log.error("Version: (Not available. CUPS may not be installed or not running.)")
        num_errors += 1

    log.info("")
##    log.info(log.bold("Checking for Reportlab..."))
##
##    try:
##        import reportlab
##        ver = reportlab.Version
##        try:
##            ver_f = float(ver)
##        except ValueError:
##            log.warn("Can't determine version.")
##        else:
##            if ver_f >= 2.0:
##                log.info("OK, version >= 2.0")
##            else:
##                log.warn("Version < 2.0 (%.1f). HPLIP fax coverpages requires Reportlab 2.0+." % ver_f)
##                num_errors += 1
##
##    except ImportError:
##        log.warn("Not installed.")
##        num_errors += 1

    if time_flag == DEPENDENCY_RUN_AND_COMPILE_TIME:
        tui.header("COMPILE AND RUNTIME DEPENDENCIES")
        log.note("To check for compile-time only dependencies, re-run hp-check with the -c parameter (ie, hp-check -c).")
        log.note("To check for run-time only dependencies, re-run hp-check with the -r parameter (ie, hp-check -r).")
    
    elif time_flag == DEPENDENCY_COMPILE_TIME:
        tui.header("COMPILE TIME DEPENDENCIES")
    
    elif time_flag == DEPENDENCY_RUN_TIME:
        tui.header("RUNTIME DEPENDENCIES")

    log.info("")

    dd = core.dependencies.keys()
    dd.sort()
    for d in dd:
        log.debug("***")

        if time_flag == DEPENDENCY_RUN_AND_COMPILE_TIME or time_flag == core.dependencies[d][4]:
                
            log.info(log.bold("Checking for dependency: %s..." % core.dependencies[d][2]))

            if core.have_dependencies[d]:
                log.info("OK, found.")
            else:
                num_errors += 1

                if core.dependencies[d][4] == DEPENDENCY_RUN_AND_COMPILE_TIME:
                    s = ''
                elif core.dependencies[d][4] == DEPENDENCY_COMPILE_TIME:
                    s = '/COMPILE TIME ONLY'
                    
                elif core.dependencies[d][4] == DEPENDENCY_RUN_TIME:
                    s = '/RUNTIME ONLY'
                
                if core.dependencies[d][0]:
                    log.error("NOT FOUND! This is a REQUIRED%s dependency. Please make sure that this dependency is installed before installing or running HPLIP." % s)
                else:
                    log.warn("NOT FOUND! This is an OPTIONAL%s dependency. Some HPLIP functionality may not function properly." %s)

                if core.distro_supported():
                    packages_to_install, commands = core.get_dependency_data(d)

                    commands_to_run = []

                    if packages_to_install:
                        package_mgr_cmd = core.get_distro_data('package_mgr_cmd')

                        if package_mgr_cmd:
                            packages_to_install = ' '.join(packages_to_install)
                            commands_to_run.append(utils.cat(package_mgr_cmd))

                    if commands:
                        commands_to_run.extend(commands)

                    overall_commands_to_run.extend(commands_to_run)

                    if len(commands_to_run) == 1:
                        log.info("To install this dependency, execute this command:")
                        log.info(commands_to_run[0])

                    elif len(commands_to_run) > 1:
                        log.info("To install this dependency, execute these commands:")
                        for c in commands_to_run:
                            log.info(c)


            log.info("")
    
    if time_flag in (DEPENDENCY_RUN_TIME, DEPENDENCY_RUN_AND_COMPILE_TIME):
        tui.header("HPLIP INSTALLATION")

        scanning_enabled = utils.to_bool(sys_cfg.configure.get("scanner-build", False))

        log.info("")
        log.info(log.bold("Currently installed HPLIP version..."))
        v = sys_cfg.hplip.version
        home = sys_cfg.dirs.home

        if v:
            log.info("HPLIP %s currently installed in '%s'." % (v, home))

            log.info("")
            log.info(log.bold("Current contents of '/usr/local/etc/hp/hplip.conf' file:"))
            output = file('/usr/local/etc/hp/hplip.conf', 'r').read()
            log.info(output)

        else:
            log.info("Not found.")  

        
        if device_avail:
            if prop.par_build:
                tui.header("DISCOVERED PARALLEL DEVICES")
                
                devices = device.probeDevices('par')
                
                if devices:
                    f = tui.Formatter()
                    f.header = ("Device URI", "Model")
                    
                    for d, dd in devices.items():
                        f.add((d, dd[0]))

                    f.output()
                
                else:
                    log.info("No devices found.")
                    
                    if not core.have_dependencies['ppdev']:
                        log.error("'ppdev' kernel module not loaded.")
            
            if prop.usb_build:
                tui.header("DISCOVERED USB DEVICES")                
                
                devices = device.probeDevices('usb')
                
                if devices:
                    f = tui.Formatter()
                    f.header = ("Device URI", "Model")
                    
                    for d, dd in devices.items():
                        f.add((d, dd[0]))

                    f.output()
                    
                else:
                    log.info("No devices found.")
        
        
        tui.header("INSTALLED CUPS PRINTER QUEUES")

        lpstat_pat = re.compile(r"""^device for (.*): (.*)""", re.IGNORECASE)

        status, output = utils.run('lpstat -v')
        log.info("")

        cups_printers = []
        for p in output.splitlines():
            try:
                match = lpstat_pat.search(p)
                printer_name = match.group(1)
                device_uri = match.group(2)
                cups_printers.append((printer_name, device_uri))
            except AttributeError:
                pass

        log.debug(cups_printers)

        if cups_printers:
            non_hp = False
            for p in cups_printers:
                printer_name, device_uri = p
                try:
                    back_end, is_hp, bus, model, serial, dev_file, host, port = \
                        parseDeviceURI(device_uri)
                except Error:
                    back_end, is_hp, bus, model, serial, dev_file, host, port = \
                        '', False, '', '', '', '', '', 1

                log.info(log.bold(printer_name))
                log.info(log.bold('-'*len(printer_name)))

                x = "Unknown"
                if back_end == 'hpfax':
                    x = "Fax"
                elif back_end == 'hp':
                    x = "Printer"

                log.info("Type: %s" % x)

                if is_hp:
                    x = 'Yes, using the %s: CUPS backend.' % back_end
                else:
                    x = 'No, not using the hp: or hpfax: CUPS backend.'
                    non_hp = True

                log.info("Installed in HPLIP?: %s" % x)
                log.info("Device URI: %s" % device_uri)

                ppd = os.path.join('/etc/cups/ppd', printer_name + '.ppd')

                if os.path.exists(ppd):
                    log.info("PPD: %s" % ppd)
                    nickname_pat = re.compile(r'''\*NickName:\s*\"(.*)"''', re.MULTILINE)

                    f = file(ppd, 'r').read(4096)

                    try:
                        desc = nickname_pat.search(f).group(1)
                    except AttributeError:
                        desc = ''

                    log.info("PPD Description: %s" % desc)

                    status, output = utils.run('lpstat -p%s' % printer_name)
                    log.info("Printer status: %s" % output.replace("\n", ""))

                    if back_end == 'hpfax' and desc != 'HP Fax':
                        num_errors += 1
                        log.error("Incorrect PPD file for fax queue '%s'. Fax queues must use 'HP-Fax-hplip.ppd'." % printer_name)

                    elif back_end == 'hp' and desc == 'HP Fax':
                        num_errors += 1
                        log.error("Incorrect PPD file for a print queue '%s'. Print queues must not use 'HP-Fax-hplip.ppd'." % printer_name)

                    elif back_end not in ('hp', 'hpfax'):
                        log.warn("Printer is not HPLIP installed. Printers must use the hp: or hpfax: CUPS backend to function in HPLIP.")
                        num_errors += 1

                if device_avail and is_hp:
                    d = None
                    try:
                        try:
                            d = device.Device(device_uri)
                        except Error:
                            log.error("Device initialization failed.")
                            continue

                        plugin = d.mq.get('plugin', PLUGIN_NONE)
                        if plugin in (PLUGIN_REQUIRED, PLUGIN_OPTIONAL):
                            home = sys_cfg.dirs.home or os.path.realpath(os.path.normpath(os.getcwd()))
                            
                            log.debug("home=%s" % home)

                            model = model.lower()

                            if os.path.exists(os.path.join(home, "data", "plugins", "%s.plugin" % model)):
                                if plugin == PLUGIN_REQUIRED:
                                    log.info("Required plug-in status: Installed")
                                else:
                                    log.info("Optional plug-in status: Installed")
                            else:
                                num_errors += 1
                                
                                if plugin == PLUGIN_REQUIRED:
                                    log.error("Required plug-in status: Not installed")
                                else:
                                    log.warn("Optional plug-in status: Not installed") 
                                

                        if bus in ('par', 'usb'):
                            try:
                                d.open()
                            except Error, e:
                                log.error(e.msg)
                                deviceid = ''
                            else:
                                deviceid = d.getDeviceID()
                                log.debug(deviceid)

                            if not deviceid:
                                #log.error("Communication status: Failed")
                                num_errors += 1
                            else:
                                log.info("Communication status: Good")

                        elif bus == 'net':
                            try:
                                error_code, deviceid = d.getPML(pml.OID_DEVICE_ID)
                            except Error:
                                #log.error("Communication with device failed.")
                                error_code = pml.ERROR_COMMAND_EXECUTION
                            
                            if error_code > pml.ERROR_MAX_OK:
                                #log.error("Communication status: Failed")
                                num_errors += 1
                            else:
                                log.info("Communication status: Good")

                    finally:
                        if d is not None:
                            d.close()

                    log.info("")



        else:
            log.warn("No queues found.")

        if scanning_enabled:
            tui.header("SANE CONFIGURATION")
            log.info(log.bold("'hpaio' in '/usr/local/etc/sane.d/dll.conf'..."))
            try:
                f = file('/usr/local/etc/sane.d/dll.conf', 'r')
            except IOError:
                log.error("'/usr/local/etc/sane.d/dll.conf' not found. Is SANE installed?")
                num_errors += 1
            else:
                found = False
                for line in f:
                    if 'hpaio' in line:
                        found = True

                if found:
                    log.info("OK, found. SANE backend 'hpaio' is properly set up.")
                else:
                    num_errors += 1
                    log.error("Not found. SANE backend 'hpaio' NOT properly setup (needs to be added to /usr/local/etc/sane.d/dll.conf).")

                log.info("")
                log.info(log.bold("Checking output of 'scanimage -L'..."))
                if utils.which('scanimage'):
                    status, output = utils.run("scanimage -L")
                    log.info(output)
                else:
                    log.error("scanimage not found.")

        tui.header("PYTHON EXTENSIONS")

        log.info(log.bold("Checking 'cupsext' CUPS extension..."))
        try:
            import cupsext
        except ImportError:
            num_errors += 1
            log.error("NOT FOUND OR FAILED TO LOAD! Please reinstall HPLIP and check for the proper installation of cupsext.")
        else:
            log.info("OK, found.")

        log.info("")
        log.info(log.bold("Checking 'pcardext' Photocard extension..."))
        try:
            import pcardext
        except ImportError:
            num_errors += 1
            log.error("NOT FOUND OR FAILED TO LOAD! Please reinstall HPLIP and check for the proper installation of pcardext.")
        else:
            log.info("OK, found.")

        log.info("")
        log.info(log.bold("Checking 'hpmudext' I/O extension..."))
        try:
            import hpmudext
            hpmudext_avail = True
        except ImportError:
            hpmudext_avail = False
            num_errors += 1
            log.error("NOT FOUND OR FAILED TO LOAD! Please reinstall HPLIP and check for the proper installation of hpmudext.")
        else:
            log.info("OK, found.")        

        if scanning_enabled:
            log.info("")
            log.info(log.bold("Checking 'scanext' SANE scanning extension..."))
            try:
                import scanext
            except ImportError:
                num_errors += 1
                log.error("NOT FOUND OR FAILED TO LOAD! Please reinstall HPLIP and check for the proper installation of scanext.")
            else:
                log.info("OK, found.")        

                log.info("")

        tui.header("USB I/O SETUP")

        if hpmudext_avail:
            lsusb = utils.which('lsusb')
            if lsusb:
                log.info("")
                log.info(log.bold("Checking for permissions of USB attached printers..."))
                lsusb = os.path.join(lsusb, 'lsusb')
                status, output = utils.run("%s -d03f0:" % lsusb)

                lsusb_pat = re.compile("""^Bus\s([0-9a-fA-F]{3,3})\sDevice\s([0-9a-fA-F]{3,3}):\sID\s([0-9a-fA-F]{4,4}):([0-9a-fA-F]{4,4})(.*)""", re.IGNORECASE)
                log.debug(output)

                for o in output.splitlines():
                    ok = True
                    match = lsusb_pat.search(o)

                    if match is not None:
                        bus, device, vid, pid, mfg = match.groups()
                        log.info("\nHP Device 0x%x at %s:%s: " % (int(pid, 16), bus, device))
                        result_code, deviceuri = hpmudext.make_usb_uri(bus, device)

                        if result_code == hpmudext.HPMUD_R_OK:
                            log.info("    Device URI: %s" %  deviceuri)
                        else:
                            log.warn("    Device URI: (Makeuri FAILED)")

                        devnode = os.path.join("/", "dev", "bus", "usb", bus, device)

                        if not os.path.exists(devnode):
                            devnode = os.path.join("/", "proc", "bus", "usb", bus, device)

                        if os.path.exists(devnode):
                            log.info("    Device node: %s" % devnode)

                            st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, \
                                st_size, st_atime, st_mtime, st_ctime = \
                                os.stat(devnode)

                            log.info("    Mode: 0%o" % (st_mode & 0777))

                            getfacl = utils.which('getfacl')
                            if getfacl:
                                getfacl = os.path.join(getfacl, "getfacl")

                                status, output = utils.run("%s %s" % (getfacl, devnode))

                                log.info(output)


    tui.header("SUMMARY")

    if num_errors:
        if num_errors == 1:
            log.error("1 error or warning.")
        else:
            log.error("%d errors and/or warnings." % num_errors)

        if overall_commands_to_run:
            log.info("")
            log.info(log.bold("Summary of needed commands to run to satisfy missing dependencies:"))
            for c in overall_commands_to_run:
                log.info(c)

        log.info("")
        log.info("Please refer to the installation instructions at:")
        log.info("http://hplip.sourceforge.net/install/index.html\n")

    else:
        log.info(log.green("No errors or warnings."))

except KeyboardInterrupt:
    log.warn("Aborted")
