#!/usr/bin/python
import dbus, sys, os, time, signal, re, glob
import traceback
import cups, cupshelpers
import subprocess
from syslog import *
try:
    import usb
except:
    pass

def find_usblp_udi(udi):
    # Return always a UDI based on the usblp kernel module (has
    # "_printer_" in it). If the input UDI is from low-level USB,
    # find the corresponding usblp UDI.
    if udi.find ("_printer_") == -1:
        devnull = file ("/dev/null", "r+")
        command = "hal-device | grep '[0-9]: *udi *= *.%s.*_printer_' | cut -d \\' -f 2" % udi;
        try:
            p = subprocess.Popen (command,
                                  shell=True,
                                  stdin=devnull,
                                  stdout=subprocess.PIPE,
                                  stderr=devnull)
            (stdout, stderr) = p.communicate ()
            udi = stdout.split ('\n')[0].strip ()
            if udi.find ("_printer_") == -1:
                return None
        except:
            return None
    return udi

def get_hal_property(udi, key):
    # Read out the value of a given key for a given HAL UDI
    devnull = file ("/dev/null", "r+")
    try:
        p = subprocess.Popen (["hal-get-property", "--udi", udi,
                               "--key", key],
                              stdin=devnull,
                              stdout=subprocess.PIPE,
                              stderr=devnull)
        (stdout, stderr) = p.communicate ()
        return stdout.strip ()
    except:
        return ""

def get_hal_uri(udi):
    # udi is the UDI under which HAL reported the printer to us. The
    # "hal" CUPS backend only works with UDIs based on the usblp
    # kernel module and not with low-level UDIs. So if we are called
    # with a low-level UDI here we try to find the corresponding usblp
    # UDI here (contains "_printer_"). Then we return a valid CUPS URI
    # for the "hal" backend ("hal://<usblp-based HAL UDI>"). If we are
    # called with a low-level UDI and do not find the corresponding
    # usblp-based UDI, we return None.
    usblp_udi = find_usblp_udi(udi)
    if usblp_udi:
        return "hal://%s" % usblp_udi
    else:
        return None

def get_hplip_uris_for_usb (fax=False, checkuri=None):
    hpuris = []
    env = dict()
    env.update (os.environ)
    env['LC_ALL'] = 'C'
    devnull = file ("/dev/null", "r+")
    try:
        p = subprocess.Popen (['lsusb'], env=env,
                              stdin=devnull,
                              stdout=subprocess.PIPE,
                              stderr=devnull)
        (stdout, stderr) = p.communicate ()
        lsusboutput = stdout.split ('\n')
    except:
        if checkuri:
            return False
        else:
            return hpuris
    for line in lsusboutput:
        if (line.find ("ID 03f0:") < 0): continue
        bus = line[4:7]
        device = line[15:18]
        if fax:
            type="-f"
        else:
            type="-c"
        try:
            p = subprocess.Popen (["hp-makeuri", "-lnone",
                                   type, "%s:%s" % (bus, device)], env=env,
                                  stdin=devnull, 
                                  stdout=subprocess.PIPE,
                                  stderr=devnull)
            (stdout, stderr) = p.communicate ()
            uri = stdout.split ('\n')[0].strip ()
        except:
            continue
        if (not uri): continue
        if checkuri and checkuri == uri:
             return True
        hpuris.append (uri)
    if checkuri:
        return False
    else:
        return hpuris

class HalPrinter:
    def __init__(self):
        self.get_properties()
        self.uris = None
        self.hp_fax_uris = None
        try:
            self.cups_connection = cups.Connection()
        except RuntimeError, e:
            syslog (LOG_ERR,
                    "Unable to connect to CUPS: '%s'.  Is CUPS running?" % e)
            sys.exit (1)

    def get_properties(self):
        self.properties = {}
        for key, value in os.environ.iteritems():
            if key.startswith("HAL_PROP_"):
                name = key[9:].lower().replace("_", '.')
                self.properties[name] = value
        self.uid = os.getenv("UDI", "")
        if re.search ("_if\d+$", self.uid):
            syslog (LOG_DEBUG, "hal_lpadmin triggered by low-level USB device")
        else:
            syslog (LOG_DEBUG, "hal_lpadmin triggered by usblp kernel module")
        self.read()

    def fetch_device_id(self):
        p = self.properties
        devidstr = ''
        buses = usb.busses()
        usb_configuration_value = p.get ("usb.configuration.value")
        usb_interface_number = p.get ("usb.interface.number")
        for bus in buses:
            if int (bus.dirname) != int (p.get ("usb.bus.number")):
                continue

            for dev in bus.devices:
                if int (dev.filename) != int (p.get("usb.linux.device.number")):
                    continue

                for config in dev.configurations:
                    if (usb_configuration_value != None and
                        (int (config.value) != int (usb_configuration_value))):
                        continue

                    for intf in config.interfaces:
                        interfaceNumber = int (intf[0].interfaceNumber)
                        if (usb_interface_number != None and
                            int (interfaceNumber) != 
                            int (usb_interface_number)):
                            continue

                        syslog (LOG_DEBUG, "Device %s:%s: %s" %
                                (bus.dirname,
                                 dev.filename,
                                 p.get ("info.udi")))
                        handle = dev.open ()
                        #handle.setConfiguration (config)
                        handle.claimInterface (intf[0])
                        handle.setAltInterface (intf[0])
                        intfno = intf[0].interfaceNumber
                        alt = intf[0].alternateSetting
                        devidarr = handle.controlMsg (requestType = 0xa1, 
                                                      request = 0,
                                                      value = config.value - 1,
                                                      index = (alt +
                                                               (intfno << 8)),
                                                      buffer = 4096,
                                                      timeout = 100)
                        siz = len(devidarr)
                        len0 = devidarr[0]
                        len1 = devidarr[1]
                        devidlen  = (((len0 & 255) << 8) +
                                     (len1 & 255))
                        devidlen2 = (((len1 & 255) << 8) +
                                     (len0 & 255))
                        if devidlen > siz:
                            if devidlen2 > siz:
                                devidlen = siz
                            else:
                                devidlen = devidlen2
                        devidstr = ""
                        for i in devidarr[2:]:
                            devidstr += chr (i)
                        syslog (LOG_DEBUG, "Device ID for %s:%s: %s" %
                                (bus.dirname,
                                 dev.filename,
                                 devidstr))
                        break

                    if len (devidstr) > 0:
                        break

                    if usb_configuration_value != None:
                        break
                break
            break

        return devidstr

    def read(self):
        p = self.properties
        fetch_id = False
        if p.get ("printer.vendor") == None or \
           p.get ("printer.product") == None or \
           p.get ("printer.description") == None or \
           p.get ("printer.commandset") == None:
            fetch_id = True
            usblp_udi = find_usblp_udi(self.uid)
            if usblp_udi:
                syslog (LOG_DEBUG,
                        "Getting device ID from the usblp HAL entry ...")
                devidstr = "MFG:%s;MDL:%s;DES:%s;CMD:%s;" % \
                    (get_hal_property(usblp_udi, "printer.vendor"),
                     get_hal_property(usblp_udi, "printer.product"),
                     get_hal_property(usblp_udi, "printer.description"),
                     get_hal_property(usblp_udi, "printer.commandset").\
                         replace(" ", ","))
                syslog (LOG_DEBUG, "Device ID for %s: %s" %
                        (get_hal_property(usblp_udi, "printer.device"),
                         devidstr))
            else:
                # Printer 1284 device URI not supplied by HAL, so we poll
                # it from the printer and add it to the HAL database
                # entry. This happens usually if the printer is detected
                # via low-level USB.
                if p.get ("usb.bus.number") == None or \
                   p.get ("usb.linux.device.number") == None:
                    fetch_id = False
                if fetch_id:
                    try: 
                        syslog (LOG_DEBUG,
                                "Polling device ID from the printer ...")
                        devidstr = self.fetch_device_id ()
                    except Exception, e:
                        # We cannot read out the device ID from the
                        # device, probably because the device is not
                        # really a printer. So stop hal_lpadmin here
                        # ignoring the device.
                        syslog (LOG_DEBUG, "Failed to fetch device ID: %s" %
                                str (e))
                        syslog (LOG_DEBUG, "Ignoring device with UDI %s" %
                                self.uid)
                        sys.exit(0)

        if fetch_id:
            # Write it into the HAL database.
            try:
                self.device_id = devidstr
                id_dict = cupshelpers.parseDeviceID (devidstr)
                self.make = (id_dict["MFG"] or p.get("usb.vendor", "Unknown"))
                p["printer.vendor"] = self.make
                self.model = (id_dict["MDL"] or p.get("usb.product", "Unknown"))
                p["printer.product"] = self.model
                self.description = id_dict["DES"]
                p["printer.description"] = self.description
                self.name = self.get_name()
                self.faxname = self.name + "_fax"
                self.commandsets = id_dict["CMD"]
                p["printer.commandset"] = '\t'.join (self.commandsets)
                self.serial = (id_dict["SN"] or p.get("usb.serial", None))
                if self.serial != None:
                    p["printer.serial"] = self.serial

                devnull = file ("/dev/null", "r+")
                env = dict()
                env.update (os.environ)
                env["LC_ALL"] = "C"
                    
                for key in ("printer.vendor", "printer.product",
                            "printer.description", "printer.serial"):
                    if p.get (key) == None:
                        continue
                    try:
                        pr = subprocess.Popen (["hal-set-property",
                                               "--udi=%s" % self.uid,
                                               "--key=%s" % key,
                                               "--string=%s" % p[key]],
                                              env=env,
                                              stdin=devnull,
                                              stdout=devnull,
                                              stderr=subprocess.STDOUT)
                        pr.wait ()
                    except:
                        pass
                for cs in self.commandsets:
                    try:
                        pr = subprocess.Popen (["hal-set-property",
                                               "--udi=%s" % self.uid,
                                               "--key=printer.commandset",
                                               "--strlist-post=%s" % cs],
                                              env=env,
                                              stdin=devnull,
                                              stdout=devnull,
                                              stderr=subprocess.STDOUT)
                        pr.wait ()
                    except:
                        pass
                syslog (LOG_DEBUG,
                        "Written device ID into HAL database entry: "
                        "MFG:%s;MDL:%s;DES:%s;CMD:%s;" %
                        (p["printer.vendor"], p["printer.product"],
                         p["printer.description"],
                         p["printer.commandset"].replace("\t", ",")))
            except Exception, e:
                syslog (LOG_DEBUG,
                        "Failed to write device ID into HAL database entry: %s"
                        % str (e))
                fetch_id = False

        if not fetch_id:
            # Printer device ID is already available in the HAL database.
            # Either HAL has already put it there (triggered by the usblp
            # kernel module) or hal_lpadmin has read it from the printer
            # when it was turned on.
            syslog (LOG_DEBUG, "Using device ID from HAL database entry")
            self.make = (p.get("printer.vendor", "") or
                     p.get("usb.vendor", "Unknown"))
            self.model = (p.get("printer.product", "") or
                      p.get("usb.product", "Unknown"))
            self.description = p.get("printer.description", "")
            self.name = self.get_name()
            self.faxname = self.name + "_fax"
            self.commandsets = p.get('printer.commandset', '').split('\t')
            self.serial = p.get("printer.serial", "")

            # Reconstruct Device ID ready to put it into the PPD file.
            devidstr = ''
            for (field, value) in [("MFG:", self.make),
                                   ("MDL:", self.model),
                                   ("DES:", self.description),
                                   ("CMD:", reduce (lambda x, y: x + ',' + y,
                                                    self.commandsets)),
                                   ("SN:", self.serial)]:
                if len (value) > 0:
                    devidstr += field + value + ";"

            self.device_id = devidstr

    def get_name(self):
        # XXX check for unallowed chars
        if self.properties.has_key("usb.port_number"):
            name = "%s-%s" % (self.model,
                              self.properties["usb.port_number"])
        else:
            name = self.model
        name = name.replace(" ", "-")
        name = name.replace("/", "-")
        return name.replace("#", "-")

    def get_cups_uris(self, removed=False):
        if self.uris != None:
            return self.uris
        uris = []
        available_usb_uris = []
        for backend in ("hal", "usb", "hp"):
            devnull = file ("/dev/null", "r+")
            command = "/usr/lib/cups/backend/%s | cut -s -d ' ' -f 2" % backend;
            try:
                p = subprocess.Popen (command,
                                      shell=True,
                                      stdin=devnull,
                                      stdout=subprocess.PIPE,
                                      stderr=devnull)
                (stdout, stderr) = p.communicate ()
                available_usb_uris = stdout.split ('\n') + \
                    available_usb_uris
            except:
                pass
        udi = self.uid.lower ()
        make = ''
        model = ''
        serial = ''
        if self.properties.has_key("printer.vendor"):
            make = self.properties["printer.vendor"].lower ()
            if make == "hewlett-packard":
                make = "hp"
            elif make == "lexmark international":
                make = "lexmark"
        if self.properties.has_key("printer.product"):
             model = self.properties["printer.product"].lower ()
             if make and model.startswith (make):
                 model = model[len (make):]
                 model = model.lstrip ()
             model = model.rstrip ()
        if self.properties.has_key("printer.serial"):
             serial = self.properties["printer.serial"].lower ()
        for uri in available_usb_uris:
            if uri.lower ().find (udi) != -1:
                uris.append (uri)
            elif uri.lower ().find (serial) != -1:
                uris.append (uri)
            else:
                # If we arrive here, the printer's serial number did not
                # match the currently investigated URI. Skip this URI
                # if it contains another serial number
                if uri.lower().find("?serial=") != -1: continue
                nuri = re.sub(r"(\%\d\d)+", " ", uri.lower())
                nuri = re.sub(r"[^a-z0-9]+", " ", nuri)
                nmake = re.sub(r"[^a-z0-9]+", " ", make)
                nmodel = re.sub(r"[^a-z0-9]+", " ", model)
                if nuri.find (nmake) != -1 and nuri.find (nmodel) != -1:
                    uris.append (uri)
        self.uris = uris
        return uris

    def get_cups_uri(self):
        try:
            return self.get_cups_uris()[0]
        except:
            return None

    def get_cups_hp_fax_uris(self, removed=False):
        if self.hp_fax_uris != None:
            return self.hp_fax_uris
        faxurisfound = 0
        if self.properties.has_key("printer.vendor"):
            vendor = self.properties["printer.vendor"].lower ()
            if (not removed and
                (vendor == "hewlett-packard" or vendor == "hp")):
                # We only can have a fax URI if we have an HP printer
                # supported by HPLIP
                try:
                    # Try to find a matching HPLIP fax URI for the HPLIP
                    # print URI for this device
                    hpfaxuris = get_hplip_uris_for_usb (True)
                    uris = self.get_cups_uris ()
                    faxuris = []
                    for uri in uris:
                        if not uri.startswith ("hp:"): continue
                        faxuri = uri.replace("hp:/", "hpfax:/")
                        for furi in hpfaxuris:
                            if faxuri == furi:
                                faxurisfound = 1
                                faxuris.append (faxuri)
                except:
                    pass
        
        if faxurisfound == 1:
            self.hp_fax_uris = faxuris
            return faxuris
        else:
            return None

    def get_cups_hp_fax_uri(self):
        faxuris = self.get_cups_hp_fax_uris()
        if faxuris:
            return faxuris[0]
        else:
            return None

    def store_device_id_in_ppd(self):
        fname = self.cups_connection.getPPD(self.name)
        lines = file (fname).readlines ()
        attr = "*1284DeviceID:"
        has_1284_attr = reduce (lambda x, y: x or y,
                                map (lambda x: x.startswith (attr)))
        outf = file (fname, 'w')
        written = False
        attrline = attr + ' ' + self.device_id + '\n'
        for line in lines:
            if not written:
                if has_1284_attr:
                    if line.startswith (attr):
                        # Replace existing attribute.
                        line = attrline
                        written = True
                else:
                    if line == '\n':
                        # Write attribute before first blank line.
                        line = attrline + line
                        written = True

            outf.write (line)
        if not written:
            outf.write (attrline)
        outf.close ()
        self.cups_connection.addPrinter(self.name, filename=fname)
        os.unlink (fname)

    def printer_is_our_hal_printer(self, name, printer):
        make = self.properties.get ("printer.vendor", None)
        model = self.properties.get ("printer.product", None)
        serial = self.properties.get ("printer.serial", None)
        if not serial:
            serial = self.properties.get ("info.udi", None)
            if not serial:
                serial = self.properties.get ("info.parent", None)
            if serial:
                res = re.search ("usb_device_[0-9a-fA-F]+_[0-9a-fA-F]+_([0-9a-zA-Z]+)", serial)
                if res:
                    resg = res.groups()
                    serial = resg[0]
        bus = self.properties.get ("linux.subsystem", None)
        udi = self.properties.get ("info.udi", None)
        if make:
            makel = make.lower ()
            if makel == "hewlett-packard":
                make = "HP"
            elif makel == "lexmark international":
                make = "Lexmark"
        if model:
            if model.startswith (make):
                model = model[len (make):]
                model = model.lstrip ()
        if printer.is_class:
            return False
        if (((model and 
              (printer.device_uri.find (model.replace (" ", "%20")) \
                   != -1 or
               printer.device_uri.find (model.replace (" ", "_")) \
                   != -1)) and
             (not serial or printer.device_uri.find ("serial=") == -1 or
              (serial and
               printer.device_uri.find ("serial=" + serial) != -1)) and
             (not bus or
              printer.device_uri.find (bus) != -1)) or
            (udi and printer.device_uri.find (udi) != -1) or
            (serial and
             printer.device_uri.find ("serial=" + serial) != -1)):
            syslog (LOG_DEBUG,
                    "Found configured printer: %s; URI: %s" % 
                    (name, printer.device_uri))
            return True
        return False

    def add(self):
        syslog (LOG_DEBUG, "add")
        make = self.properties.get ("printer.vendor", None)
        model = self.properties.get ("printer.product", None)
        serial = self.properties.get ("printer.serial", None)
        syslog (LOG_DEBUG, "Printer reported by HAL: %s %s %s" % (make, model, serial))
        printers = cupshelpers.getPrinters(self.cups_connection)
        printers_extra_info = None
        printer_exists = 0
        fax_exists = 0
        for name, printer in printers.iteritems():
            if self.printer_is_our_hal_printer(name, printer):
                if printer.device_uri.startswith("hpfax:"):
                    fax_exists = 1
                else:
                    printer_exists = 1
                syslog (LOG_DEBUG,
                        "Not adding printer: %s already exists" % name)
                printer_exists = 1
                if not printer.enabled:
                    if printers_extra_info == None:
                        printers_extra_info = self.cups_connection.getPrinters()
                    statemsg = printers_extra_info[name]["printer-state-message"]
                    if statemsg.lower ().startswith ("unplugged"):
                        syslog (LOG_INFO,
                                "Re-enabling printer %s" % name)
                        self.cups_connection.enablePrinter(name)
                    else:
                        syslog (LOG_INFO,
                                "Printer %s exists but is disabled, reason: %s; "
                                "use 'cupsenable %s' to enable it" % (name, statemsg, name))

        # Get some info needed to create CUPS queues and stop here if no new
        # queues are needed
        uris = self.get_cups_uris ()
        faxuris = self.get_cups_hp_fax_uris ()
        if printer_exists and (fax_exists or not faxuris):
            syslog (LOG_INFO, "No print queue setup needed for printer with UDI %s, as queues are already in place." % self.uid)
            return
        if not uris:
            syslog (LOG_INFO, "Could not treat printer with UDI %s, no CUPS URI found for it." % self.uid)
            return
        ppds = None
        syslog (LOG_DEBUG, "URIs: %s" % uris)
        syslog (LOG_DEBUG, "HPLIP Fax URIs: %s" % faxuris)

        # Make the name unique.
        if self.name in printers.keys ():
            suffix = 2
            while (self.name + str (suffix)) in printers.keys ():
                suffix += 1
                if suffix == 100:
                    break
            self.name += str (suffix)

        # Make the faxname unique
        if self.faxname in printers.keys ():
            suffix = 2
            while (self.faxname + str (suffix)) in printers.keys ():
                suffix += 1
                if suffix == 100:
                    break
            self.faxname += str (suffix)

        def wait_child (sig, stack):
            (pid, status) = os.wait ()

        signal.signal (signal.SIGCHLD, wait_child)
        pid = os.fork ()
        if pid == 0:
            # Child.
            if fax_exists == 0:
                # really new fax printer
                faxuri = self.get_cups_hp_fax_uri()
            else:
                faxuri = None

            if printer_exists == 0 or faxuri:
                # really new printer or fax - show tray icon with magnifier
                bus = dbus.SystemBus()
                try:
                    syslog (LOG_DEBUG, "Calling GetReady")
                    obj = bus.get_object("com.redhat.NewPrinterNotification",
                                         "/com/redhat/NewPrinterNotification")
                    notification = dbus.Interface(obj,
                                                  "com.redhat.NewPrinterNotification")
                    notification.GetReady ()
                except dbus.DBusException, e:
                    syslog (LOG_DEBUG, "D-Bus method call failed: %s" % e)
                    notification = None
            else:
                notification = None

            status = 999
            if printer_exists == 0:
                # really new printer

                # If it is an HP printer, check whether it requires a
                # plugin and do not create a queue if it does
                plugin_needed = -1
                uri = self.get_cups_uri()
                if uri and uri.startswith ("hp:"):
                    env = dict()
                    env.update (os.environ)
                    env['LC_ALL'] = 'C'
                    devnull = file ("/dev/null", "r+")
                    try:
                        p = subprocess.Popen (["hp-info", "-i",
                                               "-d%s" % uri],
                                              env=env,
                                              stdin=devnull,
                                              stdout=subprocess.PIPE,
                                              stderr=devnull)
                        properties = p.stdout.read ().split ("\n")
                    except:
                        # Problem executing command.
                        plugin_needed = 0
                    if plugin_needed < 0:
                        hplip_version = None
                        for line in properties:
                            if line.find ("plugin ") >= 0:
                                res = re.search ("(\d+)", line)
                                if res:
                                    resg = res.groups()
                                    plugin_needed = int(resg[0])
                            elif line.find ("HP Linux Imaging and Printing") >= 0:
                                res = re.search ("(\d+\.\d+\.\d+\w*)", line)
                                if res:
                                    resg = res.groups()
                                    hplip_version = resg[0]
                            if plugin_needed >= 0:
                                break
                        if plugin_needed > 0 and hplip_version:
                            # Check whether the plugin is already installed
                            if glob.glob("/usr/share/hplip/data/plugin/*%s*plugin*" %
                                         hplip_version):
                                if hplip_version.startswith("2"):
                                    try:
                                        f = open('/etc/hp/hplip.conf', 'r')
                                        for line in f:
                                            if line.strip ().startswith("plugin") and \
                                                    line.strip ().endswith("1"):
                                                f.close()
                                                plugin_needed = 0
                                                f.close()
                                    except:
                                        pass
                                else:
                                    plugin_needed = 0
                if uri and plugin_needed <= 0:
                    # No HPLIP supported printer or no plugin needed, try to
                    # find a driver and set up the print queue
                    if ppds == None:
                        cupsppds = self.cups_connection.getPPDs ()
                        ppds = cupshelpers.ppds.PPDs (cupsppds)
                    syslog (LOG_DEBUG, "Device ID: MFG:%s;MDL:%s;DES:%s;CMD:%s; URI:%s" %
                            (self.make, self.model, self.description,
                             reduce(lambda x, y: x + ',' + y, self.commandsets),
                             uri))
                    (status, ppdname) = \
                        ppds.getPPDNameFromDeviceID (self.make, self.model,
                                                     self.description,
                                                     self.commandsets,
                                                     uri)
                    syslog (LOG_DEBUG, "PPD: %s; Status: %d" % (ppdname, status))

                    info = "%s %s" % (self.make, self.model)
                    self.device = uri

                    if status == 0:
                        self.cups_connection.addPrinter(self.name,
                                                        device=self.device,
                                                        ppdname=ppdname,
                                                        info=info,
                                                        location=os.uname ()[1])
                        try:
                            self.store_device_id_in_ppd ()
                        except:
                            pass

                        cupshelpers.activateNewPrinter (self.cups_connection, self.name)
                        syslog (LOG_INFO, "Added printer %s" % self.name)
                    else:
                        self.name = self.device
                        syslog (LOG_INFO, "Did not add printer with URI %s, no exact model fit found" % self.name)
                else:
                    self.name = uri
                    syslog (LOG_INFO, "Did not add printer with URI %s, requires HPLIP plugin" % self.name)

            if faxuri:
                faxname = self.faxname
                if p == None:
                    cupsppds = self.cups_connection.getPPDs ()
                    p = cupshelpers.ppds.PPDs (cupsppds)
                (status, faxppd) = p.getPPDNameFromDeviceID ("HP", "Fax",
                                                        "HP Fax", [], faxuri)
                info = "Fax queue for %s %s" % (self.make, self.model)
                self.cups_connection.addPrinter(faxname, device=faxuri,
                                                ppdname=faxppd, info=info,
                                                location=os.uname()[1])
                self.cups_connection.enablePrinter(faxname)
                self.cups_connection.acceptJobs(faxname)
                syslog (LOG_INFO, "Added fax printer %s" % faxname)

            if notification:
                if faxuri and printer_exists != 0:
                    # Only fax queue
                    n = faxname
                    m = self.model + " (Fax)"
                else:
                    n = self.name
                    m = self.model
                try:
                    notification.NewPrinter (status, n,
                                             self.make, m,
                                             self.description,
                                             reduce(lambda x, y: x + ',' + y,
                                                    self.commandsets))
                except dbus.DBusException:
                    pass

        elif pid == -1:
            pass # should handle error

    def remove(self):
        syslog (LOG_DEBUG, "remove")
        # Disable all print queues which print to the device which
        # we detected as having been removed. This prevents from
        # jobs being retried every 30 seconds. The jobs wait in the
        # queue until the device is reconnected and turned on.
        #
        # We cannot ask CUPS for the HPLIP URIs after having unplugged or
        # turned off the printer. So we take model name and serial number
        # provided by HAL and search the print queues whose URIs contain
        # this model name and serial number. These are then the queues
        # which we will disable.
        make = self.properties.get ("printer.vendor", None)
        model = self.properties.get ("printer.product", None)
        serial = self.properties.get ("printer.serial", None)
        syslog (LOG_DEBUG, "Printer reported by HAL: %s %s %s" % (make, model, serial))
        printers = cupshelpers.getPrinters(self.cups_connection)
        for name, printer in printers.iteritems():
            if self.printer_is_our_hal_printer(name, printer):
                if printer.enabled:
                    # Check whether the parent UDI also disappeared
                    parentdied = False
                    parentudi = self.properties.get ("info.parent", None)
                    if (parentudi and re.search ("_if\d+$", parentudi)):
                        devnull = file ("/dev/null", "r+")
                        try:
                            pr = subprocess.Popen (["hal-device", parentudi],
                                                   stdin=devnull,
                                                   stdout=devnull,
                                                   stderr=subprocess.STDOUT)
                            pr.wait ()
                            parentdied = (pr.returncode == 1);
                        except:
                            pass
                    # Consider the device disconnected if either we were
                    # triggered by the low-level UDI disappearing or in
                    # case that we were triggered by the high-level (usblp
                    # kernel module) UDI disappearing that then our parent
                    # UDI (which is the low-level UDI) also disappeared.
                    udi = self.properties.get ("info.udi", None)
                    if (udi and re.search ("_if\d+$", udi)) or parentdied:
                        self.cups_connection.disablePrinter(name,
                                                            "Unplugged or turned off")
                        syslog (LOG_INFO,
                                "Disabled printer %s, as the corresponding device was unplugged or turned off" % (name))

    def configure(self):
        syslog (LOG_DEBUG, "configure")
        make, model = sys.stdin.readlines()
        if make[-1]=="\n": make = make[:-1]
        if model[-1]=="\n": model = model[:-1]

        cupsppds = self.cups_connection.getPPDs ()
        p = cupshelpers.ppds.PPDs (cupsppds)
        (status, ppdname) = p.getPPDNameFromDeviceID (make, model, "", "")

        if not ppdname:
            syslog (LOG_ERR,
                    "User-selected make/model \"%s\" \"%s\" not found" %
                    (make, model))
            return

        # add printer
        self.cups_connection.addPrinter(
            self.name, device=self.get_cups_uri(),
            ppdname=ppdname, info="Added by HAL",
            location=os.uname()[1])
        self.cups_connection.enablePrinter(self.name)
        self.cups_connection.acceptJobs(self.name)
        syslog (LOG_INFO,
                "Added printer %s with user-selected make/model" % self.name)

class HalLpAdmin:

    def __init__(self):
        if len(sys.argv)!=2:
            return self.usage()

        if sys.argv[1]=="--add":
            self.addPrinter()
        elif sys.argv[1]=="--remove":
            self.removePrinter()
        elif sys.argv[1]=="--configure":
            self.configurePrinter()
        else:
            return self.usage()

    def usage(self):
        print "Usage: hal_lpadmin (--add|--remove|--configure)"

    def addPrinter(self):
        printer = HalPrinter()
        if printer.make:
            printer.add()
        
    def removePrinter(self):
        printer = HalPrinter()
        if printer.make:
            printer.remove()

    def configurePrinter(self):
        printer = HalPrinter()
        if printer.make:
            printer.configure()

def main():
    openlog ("hal_lpadmin", 0, LOG_DAEMON)
    syslog (LOG_DEBUG, "Running hal_lpadmin")
    time.sleep (1) # Give HPLIP a chance to reconnect
    try:
        h = HalLpAdmin()
    except SystemExit, e:
        sys.exit (e)
    except:
        (type, value, tb) = sys.exc_info ()
        tblast = traceback.extract_tb (tb, limit=None)
        if len (tblast):
            tblast = tblast[:len (tblast) - 1]
        for line in traceback.format_tb (tb):
            syslog (LOG_ERR, line.strip ())
        extxt = traceback.format_exception_only (type, value)
        syslog (LOG_ERR, extxt[0].strip ())

main()
