Index: /pykota/trunk/pykota/accounters/hardware.py
===================================================================
--- /pykota/trunk/pykota/accounters/hardware.py (revision 2203)
+++ /pykota/trunk/pykota/accounters/hardware.py (revision 2205)
@@ -24,252 +24,10 @@
 
 import os
-import socket
-import time
 import signal
 import popen2
 
 from pykota.accounter import AccounterBase, PyKotaAccounterError
+from pykota.accounters import snmp, pjl
 
-ITERATIONDELAY = 1.0   # 1 Second
-STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
-
-try :
-    from pysnmp.asn1.encoding.ber.error import TypeMismatchError
-    from pysnmp.mapping.udp.error import SnmpOverUdpError
-    from pysnmp.mapping.udp.role import Manager
-    from pysnmp.proto.api import alpha
-except ImportError :
-    hasSNMP = 0
-else :    
-    hasSNMP = 1
-    pageCounterOID = ".1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1
-    pageCounterOID2 = ".1.3.6.1.2.1.43.10.2.1.5.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.5.1.1
-    hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1
-    printerStatusValues = { 1 : 'other',
-                            2 : 'unknown',
-                            3 : 'idle',
-                            4 : 'printing',
-                            5 : 'warmup',
-                          }
-    hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1
-    deviceStatusValues = { 1 : 'unknown',
-                           2 : 'running',
-                           3 : 'warning',
-                           4 : 'testing',
-                           5 : 'down',
-                         }  
-    hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1
-    prtConsoleDisplayBufferTextOID = ".1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1
-                          
-    #                      
-    # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB)
-    #
-    class SNMPAccounter :
-        """A class for SNMP print accounting."""
-        def __init__(self, parent, printerhostname) :
-            self.parent = parent
-            self.printerHostname = printerhostname
-            self.printerInternalPageCounter = None
-            self.printerInternalPageCounter2 = None
-            self.printerStatus = None
-            self.deviceStatus = None
-            
-        def retrieveSNMPValues(self) :    
-            """Retrieves a printer's internal page counter and status via SNMP."""
-            ver = alpha.protoVersions[alpha.protoVersionId1]
-            req = ver.Message()
-            req.apiAlphaSetCommunity('public')
-            req.apiAlphaSetPdu(ver.GetRequestPdu())
-            req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null()), \
-                                                        (pageCounterOID2, ver.Null()), \
-                                                        (hrPrinterStatusOID, ver.Null()), \
-                                                        (hrDeviceStatusOID, ver.Null()))
-            tsp = Manager()
-            try :
-                tsp.sendAndReceive(req.berEncode(), (self.printerHostname, 161), (self.handleAnswer, req))
-            except SnmpOverUdpError, msg :    
-                self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn")
-            tsp.close()
-    
-        def handleAnswer(self, wholeMsg, notusedhere, req):
-            """Decodes and handles the SNMP answer."""
-            self.parent.filter.logdebug("SNMP message : '%s'" % repr(wholeMsg))
-            ver = alpha.protoVersions[alpha.protoVersionId1]
-            rsp = ver.Message()
-            try :
-                rsp.berDecode(wholeMsg)
-            except TypeMismatchError, msg :    
-                self.parent.filter.printInfo(_("SNMP message decoding error for printer %s : %s") % (self.printerHostname, msg), "warn")
-            else :
-                if req.apiAlphaMatch(rsp):
-                    errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus()
-                    if errorStatus:
-                        self.parent.filter.printInfo(_("Problem encountered while doing SNMP queries on printer %s : %s") % (self.printerHostname, errorStatus), "warn")
-                    else:
-                        self.values = []
-                        for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList():
-                            self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value)
-                        try :    
-                            # keep maximum value seen for printer's internal page counter
-                            self.printerInternalPageCounter = max(self.printerInternalPageCounter, self.values[0])
-                            self.printerInternalPageCounter2 = max(self.printerInternalPageCounter2, self.values[1])
-                            self.printerStatus = self.values[2]
-                            self.deviceStatus = self.values[3]
-                            self.parent.filter.logdebug("SNMP answer is decoded : PageCounters : (%s, %s)  PrinterStatus : %s  DeviceStatus : %s" % tuple(self.values))
-                        except IndexError :    
-                            self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values))
-                            pass
-                        else :    
-                            return 1
-                        
-        def waitPrinting(self) :
-            """Waits for printer status being 'printing'."""
-            firstvalue = None
-            while 1:
-                self.retrieveSNMPValues()
-                statusAsString = printerStatusValues.get(self.printerStatus)
-                if statusAsString in ('printing', 'warmup') :
-                    break
-                if self.printerInternalPageCounter is not None :    
-                    if firstvalue is None :
-                        # first time we retrieved a page counter, save it
-                        firstvalue = self.printerInternalPageCounter
-                    else :     
-                        # second time (or later)
-                        if firstvalue < self.printerInternalPageCounter :
-                            # Here we have a printer which lies :
-                            # it says it is not printing or warming up
-                            # BUT the page counter increases !!!
-                            # So we can probably quit being sure it is printing.
-                            self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
-                            break
-                self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)    
-                time.sleep(ITERATIONDELAY)
-            
-        def waitIdle(self) :
-            """Waits for printer status being 'idle'."""
-            idle_num = idle_flag = 0
-            while 1 :
-                self.retrieveSNMPValues()
-                pstatusAsString = printerStatusValues.get(self.printerStatus)
-                dstatusAsString = deviceStatusValues.get(self.deviceStatus)
-                idle_flag = 0
-                if (pstatusAsString == 'idle') or \
-                   ((pstatusAsString == 'other') and \
-                    (dstatusAsString == 'running')) :
-                    idle_flag = 1       # Standby / Powersave is considered idle
-                if idle_flag :    
-                    idle_num += 1
-                    if idle_num > STABILIZATIONDELAY :
-                        # printer status is stable, we can exit
-                        break
-                else :    
-                    idle_num = 0
-                self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)    
-                time.sleep(ITERATIONDELAY)
-                
-pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X"
-pjlStatusValues = {
-                    "10000" : "Powersave Mode",
-                    "10001" : "Ready Online",
-                    "10002" : "Ready Offline",
-                    "10003" : "Warming Up",
-                    "10004" : "Self Test",
-                    "10005" : "Reset",
-                    "10023" : "Printing",
-                    "35078" : "Powersave Mode",         # 10000 is ALSO powersave !!!
-                    "40000" : "Sleep Mode",             # Standby
-                  }
-class PJLAccounter :
-    """A class for PJL print accounting."""
-    def __init__(self, parent, printerhostname) :
-        self.parent = parent
-        self.printerHostname = printerhostname
-        self.printerInternalPageCounter = self.printerStatus = None
-        self.timedout = 0
-        
-    def alarmHandler(self, signum, frame) :    
-        """Query has timedout, handle this."""
-        self.timedout = 1
-        raise IOError, "Waiting for PJL answer timed out. Please try again later."
-        
-    def retrievePJLValues(self) :    
-        """Retrieves a printer's internal page counter and status via PJL."""
-        port = 9100
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        try :
-            sock.connect((self.printerHostname, port))
-        except socket.error, msg :
-            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
-        else :
-            try :
-                sock.send(pjlMessage)
-            except socket.error, msg :
-                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
-            else :    
-                actualpagecount = self.printerStatus = None
-                self.timedout = 0
-                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
-                    signal.signal(signal.SIGALRM, self.alarmHandler)
-                    signal.alarm(3)
-                    try :
-                        answer = sock.recv(1024)
-                    except IOError, msg :    
-                        break   # our alarm handler was launched, probably
-                    else :    
-                        readnext = 0
-                        for line in [l.strip() for l in answer.split()] : 
-                            if line.startswith("CODE=") :
-                                self.printerStatus = line.split("=")[1]
-                            elif line.startswith("PAGECOUNT") :    
-                                readnext = 1 # page counter is on next line
-                            elif readnext :    
-                                actualpagecount = int(line.strip())
-                                readnext = 0
-                    signal.alarm(0)
-                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
-        sock.close()
-        
-    def waitPrinting(self) :
-        """Waits for printer status being 'printing'."""
-        firstvalue = None
-        while 1:
-            self.retrievePJLValues()
-            if self.printerStatus in ('10023', '10003') :
-                break
-            if self.printerInternalPageCounter is not None :    
-                if firstvalue is None :
-                    # first time we retrieved a page counter, save it
-                    firstvalue = self.printerInternalPageCounter
-                else :     
-                    # second time (or later)
-                    if firstvalue < self.printerInternalPageCounter :
-                        # Here we have a printer which lies :
-                        # it says it is not printing or warming up
-                        # BUT the page counter increases !!!
-                        # So we can probably quit being sure it is printing.
-                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
-                        break
-            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)
-            time.sleep(ITERATIONDELAY)
-        
-    def waitIdle(self) :
-        """Waits for printer status being 'idle'."""
-        idle_num = idle_flag = 0
-        while 1 :
-            self.retrievePJLValues()
-            idle_flag = 0
-            if self.printerStatus in ('10000', '10001', '35078', '40000') :
-                idle_flag = 1
-            if idle_flag :    
-                idle_num += 1
-                if idle_num > STABILIZATIONDELAY :
-                    # printer status is stable, we can exit
-                    break
-            else :    
-                idle_num = 0
-            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)
-            time.sleep(ITERATIONDELAY)
-    
 class Accounter(AccounterBase) :
     def __init__(self, kotabackend, arguments) :
@@ -354,10 +112,7 @@
         cmdlower = commandline.lower()
         if cmdlower == "snmp" :
-            if hasSNMP :
-                return self.askWithSNMP(printer)
-            else :    
-                raise PyKotaAccounterError, _("Internal SNMP accounting asked, but Python-SNMP is not available. Please download it from http://pysnmp.sourceforge.net")
+            return snmp.Handler(self, printer).retrieveInternalPageCounter()
         elif cmdlower == "pjl" :
-            return self.askWithPJL(printer)
+            return pjl.Handler(self, printer).retrieveInternalPageCounter()
             
         if printer is None :
@@ -405,36 +160,2 @@
             else :        
                 return pagecounter        
-        
-    def askWithSNMP(self, printer) :
-        """Returns the page counter from the printer via internal SNMP handling."""
-        acc = SNMPAccounter(self, printer)
-        try :
-            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
-               (os.environ.get("PYKOTAACTION") != "DENY") and \
-               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
-               self.filter.jobSizeBytes :
-                acc.waitPrinting()
-            acc.waitIdle()    
-        except :    
-            if acc.printerInternalPageCounter is None :
-                raise
-            else :    
-                self.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (acc.printerInternalPageCounter, self.filter.printername), "warn")
-        return acc.printerInternalPageCounter
-        
-    def askWithPJL(self, printer) :
-        """Returns the page counter from the printer via internal PJL handling."""
-        acc = PJLAccounter(self, printer)
-        try :
-            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
-               (os.environ.get("PYKOTAACTION") != "DENY") and \
-               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
-               self.filter.jobSizeBytes :
-                acc.waitPrinting()
-            acc.waitIdle()    
-        except :    
-            if acc.printerInternalPageCounter is None :
-                raise
-            else :    
-                self.filter.printInfo(_("PJL querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (acc.printerInternalPageCounter, self.filter.printername), "warn")
-        return acc.printerInternalPageCounter
Index: /pykota/trunk/pykota/accounters/snmp.py
===================================================================
--- /pykota/trunk/pykota/accounters/snmp.py (revision 2205)
+++ /pykota/trunk/pykota/accounters/snmp.py (revision 2205)
@@ -0,0 +1,210 @@
+# PyKota
+# -*- coding: ISO-8859-15 -*-
+#
+# PyKota - Print Quotas for CUPS and LPRng
+#
+# (c) 2003, 2004, 2005 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$
+#
+#
+
+ITERATIONDELAY = 1.0   # 1 Second
+STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
+
+import sys
+import os
+import time
+
+try :
+    from pysnmp.asn1.encoding.ber.error import TypeMismatchError
+    from pysnmp.mapping.udp.error import SnmpOverUdpError
+    from pysnmp.mapping.udp.role import Manager
+    from pysnmp.proto.api import alpha
+except ImportError :
+    class Handler :
+        def __init__(self, parent, printerhostname) :
+            """Just there to raise an exception."""
+            raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/"
+else :    
+    pageCounterOID = ".1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1
+    pageCounterOID2 = ".1.3.6.1.2.1.43.10.2.1.5.1.1" # SNMPv2-SMI::mib-2.43.10.2.1.5.1.1
+    hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1
+    printerStatusValues = { 1 : 'other',
+                            2 : 'unknown',
+                            3 : 'idle',
+                            4 : 'printing',
+                            5 : 'warmup',
+                          }
+    hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1
+    deviceStatusValues = { 1 : 'unknown',
+                           2 : 'running',
+                           3 : 'warning',
+                           4 : 'testing',
+                           5 : 'down',
+                         }  
+    hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1
+    prtConsoleDisplayBufferTextOID = ".1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1
+                          
+    #                      
+    # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB)
+    #
+    class Handler :
+        """A class for SNMP print accounting."""
+        def __init__(self, parent, printerhostname) :
+            self.parent = parent
+            self.printerHostname = printerhostname
+            self.printerInternalPageCounter = None
+            self.printerInternalPageCounter2 = None
+            self.printerStatus = None
+            self.deviceStatus = None
+            
+        def retrieveSNMPValues(self) :    
+            """Retrieves a printer's internal page counter and status via SNMP."""
+            ver = alpha.protoVersions[alpha.protoVersionId1]
+            req = ver.Message()
+            req.apiAlphaSetCommunity('public')
+            req.apiAlphaSetPdu(ver.GetRequestPdu())
+            req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null()), \
+                                                        (pageCounterOID2, ver.Null()), \
+                                                        (hrPrinterStatusOID, ver.Null()), \
+                                                        (hrDeviceStatusOID, ver.Null()))
+            tsp = Manager()
+            try :
+                tsp.sendAndReceive(req.berEncode(), (self.printerHostname, 161), (self.handleAnswer, req))
+            except SnmpOverUdpError, msg :    
+                self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn")
+            tsp.close()
+    
+        def handleAnswer(self, wholeMsg, notusedhere, req):
+            """Decodes and handles the SNMP answer."""
+            self.parent.filter.logdebug("SNMP message : '%s'" % repr(wholeMsg))
+            ver = alpha.protoVersions[alpha.protoVersionId1]
+            rsp = ver.Message()
+            try :
+                rsp.berDecode(wholeMsg)
+            except TypeMismatchError, msg :    
+                self.parent.filter.printInfo(_("SNMP message decoding error for printer %s : %s") % (self.printerHostname, msg), "warn")
+            else :
+                if req.apiAlphaMatch(rsp):
+                    errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus()
+                    if errorStatus:
+                        self.parent.filter.printInfo(_("Problem encountered while doing SNMP queries on printer %s : %s") % (self.printerHostname, errorStatus), "warn")
+                    else:
+                        self.values = []
+                        for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList():
+                            self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value)
+                        try :    
+                            # keep maximum value seen for printer's internal page counter
+                            self.printerInternalPageCounter = max(self.printerInternalPageCounter, self.values[0])
+                            self.printerInternalPageCounter2 = max(self.printerInternalPageCounter2, self.values[1])
+                            self.printerStatus = self.values[2]
+                            self.deviceStatus = self.values[3]
+                            self.parent.filter.logdebug("SNMP answer is decoded : PageCounters : (%s, %s)  PrinterStatus : %s  DeviceStatus : %s" % tuple(self.values))
+                        except IndexError :    
+                            self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values))
+                            pass
+                        else :    
+                            return 1
+                        
+        def waitPrinting(self) :
+            """Waits for printer status being 'printing'."""
+            firstvalue = None
+            while 1:
+                self.retrieveSNMPValues()
+                statusAsString = printerStatusValues.get(self.printerStatus)
+                if statusAsString in ('printing', 'warmup') :
+                    break
+                if self.printerInternalPageCounter is not None :    
+                    if firstvalue is None :
+                        # first time we retrieved a page counter, save it
+                        firstvalue = self.printerInternalPageCounter
+                    else :     
+                        # second time (or later)
+                        if firstvalue < self.printerInternalPageCounter :
+                            # Here we have a printer which lies :
+                            # it says it is not printing or warming up
+                            # BUT the page counter increases !!!
+                            # So we can probably quit being sure it is printing.
+                            self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
+                            break
+                self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)    
+                time.sleep(ITERATIONDELAY)
+            
+        def waitIdle(self) :
+            """Waits for printer status being 'idle'."""
+            idle_num = idle_flag = 0
+            while 1 :
+                self.retrieveSNMPValues()
+                pstatusAsString = printerStatusValues.get(self.printerStatus)
+                dstatusAsString = deviceStatusValues.get(self.deviceStatus)
+                idle_flag = 0
+                if (pstatusAsString == 'idle') or \
+                   ((pstatusAsString == 'other') and \
+                    (dstatusAsString == 'running')) :
+                    idle_flag = 1       # Standby / Powersave is considered idle
+                if idle_flag :    
+                    idle_num += 1
+                    if idle_num > STABILIZATIONDELAY :
+                        # printer status is stable, we can exit
+                        break
+                else :    
+                    idle_num = 0
+                self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)    
+                time.sleep(ITERATIONDELAY)
+                
+        def retrieveInternalPageCounter(self) :
+            """Returns the page counter from the printer via internal SNMP handling."""
+            try :
+                if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
+                   (os.environ.get("PYKOTAACTION") != "DENY") and \
+                   (os.environ.get("PYKOTAPHASE") == "AFTER") and \
+                   self.parent.filter.jobSizeBytes :
+                    self.waitPrinting()
+                self.waitIdle()    
+            except :    
+                if self.printerInternalPageCounter is None :
+                    raise
+                else :    
+                    self.parent.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.printername), "warn")
+            return self.printerInternalPageCounter
+            
+if __name__ == "__main__" :            
+    if len(sys.argv) != 2 :    
+        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
+    else :    
+        def _(msg) :
+            return msg
+            
+        class fakeFilter :
+            def __init__(self) :
+                self.printername = "FakePrintQueue"
+                self.jobSizeBytes = 1
+                
+            def printInfo(self, msg, level="info") :
+                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
+                sys.stderr.flush()
+                
+            def logdebug(self, msg) :    
+                self.printInfo(msg, "debug")
+                
+        class fakeAccounter :        
+            def __init__(self) :
+                self.filter = fakeFilter()
+                self.protocolHandler = Handler(self, sys.argv[1])
+            
+        acc = fakeAccounter()            
+        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Index: /pykota/trunk/pykota/accounters/pjl.py
===================================================================
--- /pykota/trunk/pykota/accounters/pjl.py (revision 2205)
+++ /pykota/trunk/pykota/accounters/pjl.py (revision 2205)
@@ -0,0 +1,179 @@
+# PyKota
+# -*- coding: ISO-8859-15 -*-
+#
+# 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$
+#
+#
+
+import sys
+import os
+import socket
+import time
+import signal
+
+ITERATIONDELAY = 1.0   # 1 Second
+STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
+
+pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X"
+pjlStatusValues = {
+                    "10000" : "Powersave Mode",
+                    "10001" : "Ready Online",
+                    "10002" : "Ready Offline",
+                    "10003" : "Warming Up",
+                    "10004" : "Self Test",
+                    "10005" : "Reset",
+                    "10023" : "Printing",
+                    "35078" : "Powersave Mode",         # 10000 is ALSO powersave !!!
+                    "40000" : "Sleep Mode",             # Standby
+                  }
+                  
+class Handler :
+    """A class for PJL print accounting."""
+    def __init__(self, parent, printerhostname) :
+        self.parent = parent
+        self.printerHostname = printerhostname
+        self.printerInternalPageCounter = self.printerStatus = None
+        self.timedout = 0
+        
+    def alarmHandler(self, signum, frame) :    
+        """Query has timedout, handle this."""
+        self.timedout = 1
+        raise IOError, "Waiting for PJL answer timed out. Please try again later."
+        
+    def retrievePJLValues(self) :    
+        """Retrieves a printer's internal page counter and status via PJL."""
+        port = 9100
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try :
+            sock.connect((self.printerHostname, port))
+        except socket.error, msg :
+            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
+        else :
+            try :
+                sock.send(pjlMessage)
+            except socket.error, msg :
+                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
+            else :    
+                actualpagecount = self.printerStatus = None
+                self.timedout = 0
+                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
+                    signal.signal(signal.SIGALRM, self.alarmHandler)
+                    signal.alarm(3)
+                    try :
+                        answer = sock.recv(1024)
+                    except IOError, msg :    
+                        break   # our alarm handler was launched, probably
+                    else :    
+                        readnext = 0
+                        for line in [l.strip() for l in answer.split()] : 
+                            if line.startswith("CODE=") :
+                                self.printerStatus = line.split("=")[1]
+                            elif line.startswith("PAGECOUNT") :    
+                                readnext = 1 # page counter is on next line
+                            elif readnext :    
+                                actualpagecount = int(line.strip())
+                                readnext = 0
+                    signal.alarm(0)
+                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
+        sock.close()
+        
+    def waitPrinting(self) :
+        """Waits for printer status being 'printing'."""
+        firstvalue = None
+        while 1:
+            self.retrievePJLValues()
+            if self.printerStatus in ('10023', '10003') :
+                break
+            if self.printerInternalPageCounter is not None :    
+                if firstvalue is None :
+                    # first time we retrieved a page counter, save it
+                    firstvalue = self.printerInternalPageCounter
+                else :     
+                    # second time (or later)
+                    if firstvalue < self.printerInternalPageCounter :
+                        # Here we have a printer which lies :
+                        # it says it is not printing or warming up
+                        # BUT the page counter increases !!!
+                        # So we can probably quit being sure it is printing.
+                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
+                        break
+            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)
+            time.sleep(ITERATIONDELAY)
+        
+    def waitIdle(self) :
+        """Waits for printer status being 'idle'."""
+        idle_num = idle_flag = 0
+        while 1 :
+            self.retrievePJLValues()
+            idle_flag = 0
+            if self.printerStatus in ('10000', '10001', '35078', '40000') :
+                idle_flag = 1
+            if idle_flag :    
+                idle_num += 1
+                if idle_num > STABILIZATIONDELAY :
+                    # printer status is stable, we can exit
+                    break
+            else :    
+                idle_num = 0
+            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)
+            time.sleep(ITERATIONDELAY)
+    
+    def retrieveInternalPageCounter(self) :
+        """Returns the page counter from the printer via internal PJL handling."""
+        try :
+            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
+               (os.environ.get("PYKOTAACTION") != "DENY") and \
+               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
+               self.parent.filter.jobSizeBytes :
+                self.waitPrinting()
+            self.waitIdle()    
+        except :    
+            if self.printerInternalPageCounter is None :
+                raise
+            else :    
+                self.parent.filter.printInfo(_("PJL querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.printername), "warn")
+        return self.printerInternalPageCounter
+            
+if __name__ == "__main__" :            
+    if len(sys.argv) != 2 :    
+        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
+    else :    
+        def _(msg) :
+            return msg
+            
+        class fakeFilter :
+            def __init__(self) :
+                self.printername = "FakePrintQueue"
+                self.jobSizeBytes = 1
+                
+            def printInfo(self, msg, level="info") :
+                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
+                sys.stderr.flush()
+                
+            def logdebug(self, msg) :    
+                self.printInfo(msg, "debug")
+                
+        class fakeAccounter :        
+            def __init__(self) :
+                self.filter = fakeFilter()
+                self.protocolHandler = Handler(self, sys.argv[1])
+            
+        acc = fakeAccounter()            
+        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
