#!/usr/bin/python
# -*- coding: ISO-8859-15 -*-

# PyKota tool to hint for printer accounters
#
# PyKota - Print Quotas for CUPS and LPRng
#
# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
# 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.
#
# $Id: pkhint,v 1.10 2004/06/07 18:43:40 jalet Exp $
#
# $Log: pkhint,v $
# Revision 1.10  2004/06/07 18:43:40  jalet
# Fixed over-verbose exits when displaying help or version number
#
# Revision 1.9  2004/06/03 21:50:34  jalet
# Improved error logging.
# crashrecipient directive added.
# Now exports the job's size in bytes too.
#
# Revision 1.8  2004/05/18 14:48:47  jalet
# Big code changes to completely remove the need for "requester" directives,
# jsut use "hardware(... your previous requester directive's content ...)"
#
# Revision 1.7  2004/05/13 13:59:27  jalet
# Code simplifications
#
# Revision 1.6  2004/03/30 12:59:47  jalet
# Fixed path problem
#
# Revision 1.5  2004/02/09 13:07:06  jalet
# Should now be able to handle network + pjl printers
#
# Revision 1.4  2004/02/09 12:35:19  jalet
# De-uglyfication.
# Now works with older CUPS (1.14) which don't detect the cupspykota backend but accept it anyway.
#
# Revision 1.3  2004/02/07 13:56:03  jalet
# Help
#
# Revision 1.2  2004/02/07 13:47:55  jalet
# More warnings
#
# Revision 1.1  2004/02/07 13:45:51  jalet
# Preliminary work on the pkhint command
#
#
#

import sys
import os

from pykota import version
from pykota.tool import PyKotaTool,PyKotaToolError
from pykota.config import PyKotaConfigError
from pykota.storage import PyKotaStorageError

__doc__ = """pkhint v%s (c) 2003-2004 C@LL - Conseil Internet & Logiciels Libres
A tool to give hints on what accounting method is best for each printer.

command line usage :

  pkhint [options] [printer1 printer2 printer3 ... printerN] <file.conf

options :

  -v | --version       Prints pkhint's version number then exits.
  -h | --help          Prints this message then exits.
  
examples :                              

  $ pkhint "hp*" printer103 </etc/cups/printers.conf
  
  Will analyze your printing system to test which accounter
  is the best for each of the defined printer which
  name matches one of the parameters.
  
  If you don't pass any argument on the command line, all
  printers will be analyzed.
  
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.

Please e-mail bugs to: %s""" % (version.__version__, version.__author__)
        
SNMPTESTS = [ \
              '/usr/bin/npadmin --pagecount %(printer)s 2>/dev/null', \
              '/usr/bin/snmpget -v1 -c public -Ov %(printer)s mib-2.43.10.2.1.4.1.1 2>/dev/null | cut -f 2,2 -d " "', \
              '/usr/bin/snmpwalk -v 1 -Cc -c public %(printer)s 2>/dev/null | grep mib-2.43.10.2.1.4.1.1 | cut -d " " -f4', \
              '/usr/bin/snmpwalk -v 1 -Cc -c public -Ov %(printer)s 2>/dev/null | grep Counter32 | tail -2 | head -1 | cut -d " " -f2', \
            ]
            
NETPJLTESTS = [ \
                '/usr/share/pykota/pagecount.pl %(printer)s %(port)s 2>/dev/null', \
                '/bin/nc -w 2 %(printer)s %(port)s </usr/share/pykota/pagecount.pjl 2>/dev/null | /usr/bin/tail -2', \
              ]

class PKHint(PyKotaTool) :
    """A class to autodetect the best accounting method for printers."""
    def extractPrintersInformation(self) :    
        """Extracts printer information from the printing system.
          
           Returns a mapping { queuename : device, ... }
        """   
        printers = {}
        current_printer = None
        for line in [l.strip() for l in sys.stdin.readlines()] :
            testline = line.lower()
            if testline.startswith("<printer ") or testline.startswith("<defaultprinter ") :
                # beginning of a CUPS printer definition
                current_printer = line.split()[-1][:-1]
            elif testline.startswith("</printer ") :
                # end of a CUPS printer definition
                current_printer = None
            elif testline.startswith("deviceuri ") :
                # CUPS printer device_uri
                device = testline.split()[-1]
                if current_printer is not None :
                    printers[current_printer] = device
            else :        
                # LPRng printcap specific code here
                pass
        return printers
        
    def extractDevices(self) :    
        """Extracts the list of available CUPS devices.
        
           Returns a mapping { device : devicetype, ... }
           
           WARNING : CUPS ONLY FOR NOW
        """   
        inp = os.popen("/usr/sbin/lpinfo -v 2>/dev/null")
        deviceslist = [l.strip() for l in inp.readlines()]
        inp.close()
        devicestypes = {}
        for device in deviceslist :
            (dtype, dname) = device.split()
            devicestypes[dname] = dtype
        return devicestypes
        
    def searchDeviceType(self, devicestypes, device) :    
        """Returns the device type for current device."""
        if device.startswith("cupspykota:") :
            fulldevice = device[:]
            device = fulldevice[len("cupspykota:"):]
            if device.startswith("//") :
                device = device[2:]
        for (k, v) in devicestypes.items() :
            if device.startswith(k) :
                return v
                
    def extractDeviceFromURI(self, device) :    
        """Cleans the device URI to remove any trace of PyKota."""
        if device.startswith("cupspykota:") :
            fulldevice = device[:]
            device = fulldevice[len("cupspykota:"):]
            if device.startswith("//") :
                device = device[2:]
        try :
            (backend, destination) = device.split(":", 1) 
        except ValueError :    
            raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device
        while destination.startswith("/") :
            destination = destination[1:]
        checkauth = destination.split("@", 1)    
        if len(checkauth) == 2 :
            destination = checkauth[1]
        return destination.split("/")[0]
        
    def accepts(self, commands, printer, port=None) :    
        """Tries to get the printer's internal page counter via SNMP."""
        for command in commands :
            inp = os.popen(command % locals())
            value = inp.readline().strip()
            inp.close()
            try :
                pagecounter = int(value)
            except :    
                pass
            else :    
                return command
        
    def main(self, args, options) :
        """Main work is done here."""
        print "\nPlease wait while pkhint analyzes your printing system's configuration..."
        printers = self.extractPrintersInformation()
        devicestypes = self.extractDevices() # TODO : IT'S CUPS ONLY FOR NOW
        configuration = []
        for (printer, deviceuri) in printers.items() :
            if self.matchString(printer, args) :
                devicetype = self.searchDeviceType(devicestypes, deviceuri)
                device = self.extractDeviceFromURI(deviceuri)
                if devicetype is None :
                    sys.stderr.write("Unknown device %s for printer %s\n" % (device, printer))
                elif devicetype == "network" :
                    try :
                        hostname, port = device.split(':')
                    except ValueError :    
                        hostname = device
                        port = 9100             # TODO : may cause problems with other protocols.
                        
                    snmpcommand = self.accepts(SNMPTESTS, hostname)
                    if snmpcommand is not None :
                        accounter = 'hardware(/usr/share/pykota/waitprinter.sh %(printer)s && ' + snmpcommand + ')'
                        configuration.append((printer, accounter))
                    else :    
                        netpjlcommand = self.accepts(NETPJLTESTS, hostname, port)
                        if netpjlcommand is not None :
                            accounter = 'hardware(' + netpjlcommand + ')'
                            configuration.append((printer, accounter))
                elif devicetype == "direct" : 
                    sys.stderr.write("Can't currently handle device %s for printer %s\n" % (device, printer))
                elif devicetype == "serial" : 
                    sys.stderr.write("Can't currently handle device %s for printer %s\n" % (device, printer))
                else :
                    sys.stderr.write("Can't currently handle device %s for printer %s\n" % (device, printer))
                    
        if not configuration :            
            print "\nSorry, pkhint can't help you for now. Please configure PyKota manually."
        else :
            print "\nPut the following lines into your /etc/pykota/pykota.conf file :\n"
            for (printer, accounter) in configuration :
                print "[%s]" % printer
                print "accounter: %s" % accounter
                print    
        
if __name__ == "__main__" : 
    sys.stderr.write("BEWARE : This tool doesn't support LPRng's printcap files yet.\n")
    sys.stderr.write("This tool is currently highly experimental, so don't rely on it.\n")
    retcode = 0
    try :
        short_options = "hv"
        long_options = ["help", "version"]
        
        # Initializes the command line tool
        manager = PKHint(doc=__doc__)
        
        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
        
        # sets long options
        options["help"] = options["h"] or options["help"]
        options["version"] = options["v"] or options["version"]
        
        if options["help"] :
            manager.display_usage_and_quit()
        elif options["version"] :
            manager.display_version_and_quit()
        else :
            if not args :
                args = [ "*" ]
            retcode = manager.main(args, options)
    except SystemExit :        
        pass
    except :
        try :
            manager.crashed("pkhint failed")
        except :    
            pass
        retcode = -1

    try :
        manager.storage.close()
    except (TypeError, NameError, AttributeError) :    
        pass
        
    sys.exit(retcode)    
