root / pykota / trunk / pykota / accounters / pjl.py @ 2205

Revision 2205, 7.8 kB (checked in by jerome, 19 years ago)

Split hardware.py into a generic hardware.py and two protocol handler
submodules.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Rev Id
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3#
4# PyKota - Print Quotas for CUPS and LPRng
5#
6# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23#
24
25import sys
26import os
27import socket
28import time
29import signal
30
31ITERATIONDELAY = 1.0   # 1 Second
32STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
33
34pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X"
35pjlStatusValues = {
36                    "10000" : "Powersave Mode",
37                    "10001" : "Ready Online",
38                    "10002" : "Ready Offline",
39                    "10003" : "Warming Up",
40                    "10004" : "Self Test",
41                    "10005" : "Reset",
42                    "10023" : "Printing",
43                    "35078" : "Powersave Mode",         # 10000 is ALSO powersave !!!
44                    "40000" : "Sleep Mode",             # Standby
45                  }
46                 
47class Handler :
48    """A class for PJL print accounting."""
49    def __init__(self, parent, printerhostname) :
50        self.parent = parent
51        self.printerHostname = printerhostname
52        self.printerInternalPageCounter = self.printerStatus = None
53        self.timedout = 0
54       
55    def alarmHandler(self, signum, frame) :   
56        """Query has timedout, handle this."""
57        self.timedout = 1
58        raise IOError, "Waiting for PJL answer timed out. Please try again later."
59       
60    def retrievePJLValues(self) :   
61        """Retrieves a printer's internal page counter and status via PJL."""
62        port = 9100
63        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
64        try :
65            sock.connect((self.printerHostname, port))
66        except socket.error, msg :
67            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
68        else :
69            try :
70                sock.send(pjlMessage)
71            except socket.error, msg :
72                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
73            else :   
74                actualpagecount = self.printerStatus = None
75                self.timedout = 0
76                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
77                    signal.signal(signal.SIGALRM, self.alarmHandler)
78                    signal.alarm(3)
79                    try :
80                        answer = sock.recv(1024)
81                    except IOError, msg :   
82                        break   # our alarm handler was launched, probably
83                    else :   
84                        readnext = 0
85                        for line in [l.strip() for l in answer.split()] : 
86                            if line.startswith("CODE=") :
87                                self.printerStatus = line.split("=")[1]
88                            elif line.startswith("PAGECOUNT") :   
89                                readnext = 1 # page counter is on next line
90                            elif readnext :   
91                                actualpagecount = int(line.strip())
92                                readnext = 0
93                    signal.alarm(0)
94                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
95        sock.close()
96       
97    def waitPrinting(self) :
98        """Waits for printer status being 'printing'."""
99        firstvalue = None
100        while 1:
101            self.retrievePJLValues()
102            if self.printerStatus in ('10023', '10003') :
103                break
104            if self.printerInternalPageCounter is not None :   
105                if firstvalue is None :
106                    # first time we retrieved a page counter, save it
107                    firstvalue = self.printerInternalPageCounter
108                else :     
109                    # second time (or later)
110                    if firstvalue < self.printerInternalPageCounter :
111                        # Here we have a printer which lies :
112                        # it says it is not printing or warming up
113                        # BUT the page counter increases !!!
114                        # So we can probably quit being sure it is printing.
115                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
116                        break
117            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)
118            time.sleep(ITERATIONDELAY)
119       
120    def waitIdle(self) :
121        """Waits for printer status being 'idle'."""
122        idle_num = idle_flag = 0
123        while 1 :
124            self.retrievePJLValues()
125            idle_flag = 0
126            if self.printerStatus in ('10000', '10001', '35078', '40000') :
127                idle_flag = 1
128            if idle_flag :   
129                idle_num += 1
130                if idle_num > STABILIZATIONDELAY :
131                    # printer status is stable, we can exit
132                    break
133            else :   
134                idle_num = 0
135            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)
136            time.sleep(ITERATIONDELAY)
137   
138    def retrieveInternalPageCounter(self) :
139        """Returns the page counter from the printer via internal PJL handling."""
140        try :
141            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
142               (os.environ.get("PYKOTAACTION") != "DENY") and \
143               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
144               self.parent.filter.jobSizeBytes :
145                self.waitPrinting()
146            self.waitIdle()   
147        except :   
148            if self.printerInternalPageCounter is None :
149                raise
150            else :   
151                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")
152        return self.printerInternalPageCounter
153           
154if __name__ == "__main__" :           
155    if len(sys.argv) != 2 :   
156        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
157    else :   
158        def _(msg) :
159            return msg
160           
161        class fakeFilter :
162            def __init__(self) :
163                self.printername = "FakePrintQueue"
164                self.jobSizeBytes = 1
165               
166            def printInfo(self, msg, level="info") :
167                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
168                sys.stderr.flush()
169               
170            def logdebug(self, msg) :   
171                self.printInfo(msg, "debug")
172               
173        class fakeAccounter :       
174            def __init__(self) :
175                self.filter = fakeFilter()
176                self.protocolHandler = Handler(self, sys.argv[1])
177           
178        acc = fakeAccounter()           
179        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Note: See TracBrowser for help on using the browser.