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

Revision 3163, 13.0 kB (checked in by jerome, 17 years ago)

If skipinitialwait is set to 'yes' in pykota.conf, don't take it into
account unless the printer really is in 'idle' state. This doesn't cost
anything since we've already retrieved the page counter and idle status
a single time before the test, but will now correctly loop until the printer
is idle if it is not already for some strange reason (no paper, and
so on).

  • 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, 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23#
24
25import sys
26import os
27import socket
28import time
29import signal
30
31# NB : in fact these variables don't do much, since the time
32# is in fact wasted in the sock.recv() blocking call, with the timeout
33ITERATIONDELAY = 1   # 1 Second
34STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
35NOPRINTINGMAXDELAY = 60 # The printer must begin to print within 60 seconds byb default.
36
37# Here's the real thing :
38TIMEOUT = 5
39
40# Old method : pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X"
41# Here's a new method, which seems to work fine on my HP2300N, while the
42# previous one didn't.
43# TODO : We could also experiment with USTATUS JOB=ON and we would know for sure
44# when the job is finished, without having to poll the printer repeatedly.
45pjlMessage = "\033%-12345X@PJL USTATUS DEVICE=ON\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n@PJL USTATUS DEVICE=OFF\033%-12345X"
46pjlStatusValues = {
47                    "10000" : "Powersave Mode",
48                    "10001" : "Ready Online",
49                    "10002" : "Ready Offline",
50                    "10003" : "Warming Up",
51                    "10004" : "Self Test",
52                    "10005" : "Reset",
53                    "10023" : "Printing",
54                    "35078" : "Powersave Mode",         # 10000 is ALSO powersave !!!
55                    "40000" : "Sleep Mode",             # Standby
56                  }
57                 
58class Handler :
59    """A class for PJL print accounting."""
60    def __init__(self, parent, printerhostname, skipinitialwait=False) :
61        self.parent = parent
62        self.printerHostname = printerhostname
63        self.skipinitialwait = skipinitialwait
64        try :
65            self.port = int(self.parent.arguments.split(":")[1].strip())
66        except (IndexError, ValueError) :
67            self.port = 9100
68        self.printerInternalPageCounter = self.printerStatus = None
69        self.timedout = 0
70       
71    def alarmHandler(self, signum, frame) :   
72        """Query has timedout, handle this."""
73        self.timedout = 1
74        raise IOError, "Waiting for PJL answer timed out. Please try again later."
75       
76    def retrievePJLValues(self) :   
77        """Retrieves a printer's internal page counter and status via PJL."""
78        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
79        try :
80            sock.connect((self.printerHostname, self.port))
81        except socket.error, msg :
82            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn")
83        else :
84            self.parent.filter.logdebug("Connected to printer %s" % self.printerHostname)
85            try :
86                sock.send(pjlMessage)
87            except socket.error, msg :
88                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn")
89            else :   
90                self.parent.filter.logdebug("Query sent to %s : %s" % (self.printerHostname, repr(pjlMessage)))
91                actualpagecount = self.printerStatus = None
92                self.timedout = 0
93                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
94                    signal.signal(signal.SIGALRM, self.alarmHandler)
95                    signal.alarm(TIMEOUT)
96                    try :
97                        answer = sock.recv(1024)
98                    except IOError, msg :   
99                        self.parent.filter.logdebug("I/O Error [%s] : alarm handler probably called" % msg)
100                        break   # our alarm handler was launched, probably
101                    except socket.error, msg :
102                        self.parent.filter.printInfo(_("Problem while receiving PJL answer from %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn")
103                    else :   
104                        readnext = 0
105                        self.parent.filter.logdebug("PJL answer : %s" % repr(answer))
106                        for line in [l.strip() for l in answer.split()] : 
107                            if line.startswith("CODE=") :
108                                self.printerStatus = line.split("=")[1]
109                                self.parent.filter.logdebug("Found status : %s" % self.printerStatus)
110                            elif line.startswith("PAGECOUNT=") :   
111                                try :
112                                    actualpagecount = int(line.split('=')[1].strip())
113                                except ValueError :   
114                                    self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip())
115                                else :
116                                    self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount)
117                            elif line.startswith("PAGECOUNT") :   
118                                readnext = 1 # page counter is on next line
119                            elif readnext :   
120                                try :
121                                    actualpagecount = int(line.strip())
122                                except ValueError :   
123                                    self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip())
124                                else :
125                                    self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount)
126                                    readnext = 0
127                    signal.alarm(0)
128                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
129        sock.close()
130        self.parent.filter.logdebug("Connection to %s is now closed." % self.printerHostname)
131       
132    def waitPrinting(self) :
133        """Waits for printer status being 'printing'."""
134        try :
135            noprintingmaxdelay = int(self.parent.filter.config.getNoPrintingMaxDelay(self.parent.filter.PrinterName))
136        except (TypeError, AttributeError) : # NB : AttributeError in testing mode because I'm lazy !
137            noprintingmaxdelay = NOPRINTINGMAXDELAY
138            self.parent.filter.logdebug("No max delay defined for printer %s, using %i seconds." % (self.parent.filter.PrinterName, noprintingmaxdelay))
139        if not noprintingmaxdelay :
140            self.parent.filter.logdebug("Will wait indefinitely until printer %s is in 'printing' state." % self.parent.filter.PrinterName)
141        else :   
142            self.parent.filter.logdebug("Will wait until printer %s is in 'printing' state or %i seconds have elapsed." % (self.parent.filter.PrinterName, noprintingmaxdelay))
143        previousValue = self.parent.getLastPageCounter()
144        timebefore = time.time()
145        firstvalue = None
146        while 1:
147            self.retrievePJLValues()
148            if self.printerStatus in ('10023', '10003') :
149                break
150            if self.printerInternalPageCounter is not None :   
151                if firstvalue is None :
152                    # first time we retrieved a page counter, save it
153                    firstvalue = self.printerInternalPageCounter
154                else :     
155                    # second time (or later)
156                    if firstvalue < self.printerInternalPageCounter :
157                        # Here we have a printer which lies :
158                        # it says it is not printing or warming up
159                        # BUT the page counter increases !!!
160                        # So we can probably quit being sure it is printing.
161                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn")
162                        break
163                    elif noprintingmaxdelay and ((time.time() - timebefore) > noprintingmaxdelay) :
164                        # More than X seconds without the printer being in 'printing' mode
165                        # We can safely assume this won't change if printer is now 'idle'
166                        if self.printerStatus in ('10000', '10001', '35078', '40000') :
167                            if self.printerInternalPageCounter == previousValue :
168                                # Here the job won't be printed, because probably
169                                # the printer rejected it for some reason.
170                                self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn")
171                            else :     
172                                # Here the job has already been entirely printed, and
173                                # the printer has already passed from 'idle' to 'printing' to 'idle' again.
174                                self.parent.filter.printInfo("Printer %s has probably already printed this job !!!" % self.parent.filter.PrinterName, "warn")
175                            break
176            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName)
177            time.sleep(ITERATIONDELAY)
178       
179    def waitIdle(self) :
180        """Waits for printer status being 'idle'."""
181        idle_num = 0
182        while 1 :
183            self.retrievePJLValues()
184            if self.printerStatus in ('10000', '10001', '35078', '40000') :
185                if (self.printerInternalPageCounter is not None) \
186                   and self.skipinitialwait \
187                   and (os.environ.get("PYKOTAPHASE") == "BEFORE") :
188                    self.parent.filter.logdebug("No need to wait for the printer to be idle, it is the case already.")
189                    return 
190                idle_num += 1
191                if idle_num >= STABILIZATIONDELAY :
192                    # printer status is stable, we can exit
193                    break
194            else :   
195                idle_num = 0
196            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName)
197            time.sleep(ITERATIONDELAY)
198   
199    def retrieveInternalPageCounter(self) :
200        """Returns the page counter from the printer via internal PJL handling."""
201        try :
202            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
203               (os.environ.get("PYKOTAACTION") == "ALLOW") and \
204               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
205               self.parent.filter.JobSizeBytes :
206                self.waitPrinting()
207            self.waitIdle()   
208        except :   
209            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")
210            raise
211        return self.printerInternalPageCounter
212           
213def main(hostname) :
214    """Tries PJL accounting for a printer host."""
215    class fakeFilter :
216        """Fakes a filter for testing purposes."""
217        def __init__(self) :
218            """Initializes the fake filter."""
219            self.PrinterName = "FakePrintQueue"
220            self.JobSizeBytes = 1
221           
222        def printInfo(self, msg, level="info") :
223            """Prints informational message."""
224            sys.stderr.write("%s : %s\n" % (level.upper(), msg))
225            sys.stderr.flush()
226           
227        def logdebug(self, msg) :   
228            """Prints debug message."""
229            self.printInfo(msg, "debug")
230           
231    class fakeAccounter :       
232        """Fakes an accounter for testing purposes."""
233        def __init__(self) :
234            """Initializes fake accounter."""
235            self.arguments = "pjl:9100"
236            self.filter = fakeFilter()
237            self.protocolHandler = Handler(self, sys.argv[1])
238           
239        def getLastPageCounter(self) :   
240            """Fakes the return of a page counter."""
241            return 0
242       
243    acc = fakeAccounter()           
244    return acc.protocolHandler.retrieveInternalPageCounter()
245   
246if __name__ == "__main__" :           
247    if len(sys.argv) != 2 :   
248        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
249    else :   
250        def _(msg) :
251            return msg
252           
253        pagecounter = main(sys.argv[1])
254        print "Internal page counter's value is : %s" % pagecounter
255       
Note: See TracBrowser for help on using the browser.