root / pykota / trunk / pykota / accounters / hardware.py @ 1713

Revision 1713, 8.9 kB (checked in by jalet, 20 years ago)

Added fix for incorrect job's size when hardware accounting fails

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
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# $Log$
24# Revision 1.13  2004/09/13 16:02:45  jalet
25# Added fix for incorrect job's size when hardware accounting fails
26#
27# Revision 1.12  2004/09/06 15:42:34  jalet
28# Fix missing import statement for the signal module
29#
30# Revision 1.11  2004/08/31 23:29:53  jalet
31# Introduction of the new 'onaccountererror' configuration directive.
32# Small fix for software accounter's return code which can't be None anymore.
33# Make software and hardware accounting code look similar : will be factorized
34# later.
35#
36# Revision 1.10  2004/08/27 22:49:04  jalet
37# No answer from subprocess now is really a fatal error. Waiting for some
38# time to make this configurable...
39#
40# Revision 1.9  2004/08/25 22:34:39  jalet
41# Now both software and hardware accounting raise an exception when no valid
42# result can be extracted from the subprocess' output.
43# Hardware accounting now reads subprocess' output until an integer is read
44# or data is exhausted : it now behaves just like software accounting in this
45# aspect.
46#
47# Revision 1.8  2004/07/22 22:41:48  jalet
48# Hardware accounting for LPRng should be OK now. UNTESTED.
49#
50# Revision 1.7  2004/07/16 12:22:47  jalet
51# LPRng support early version
52#
53# Revision 1.6  2004/07/01 19:56:42  jalet
54# Better dispatching of error messages
55#
56# Revision 1.5  2004/06/10 22:42:06  jalet
57# Better messages in logs
58#
59# Revision 1.4  2004/05/24 22:45:49  jalet
60# New 'enforcement' directive added
61# Polling loop improvements
62#
63# Revision 1.3  2004/05/24 14:36:40  jalet
64# Revert to old polling loop. Will need optimisations
65#
66# Revision 1.2  2004/05/18 14:49:22  jalet
67# Big code changes to completely remove the need for "requester" directives,
68# jsut use "hardware(... your previous requester directive's content ...)"
69#
70# Revision 1.1  2004/05/13 13:59:30  jalet
71# Code simplifications
72#
73#
74#
75
76import sys
77import os
78import signal
79import popen2
80from pykota.accounter import AccounterBase, PyKotaAccounterError
81
82class Accounter(AccounterBase) :
83    def __init__(self, kotabackend, arguments) :
84        """Initializes querying accounter."""
85        AccounterBase.__init__(self, kotabackend, arguments)
86        self.isSoftware = 0
87       
88    def getPrinterInternalPageCounter(self) :   
89        """Returns the printer's internal page counter."""
90        self.filter.logdebug("Reading printer's internal page counter...")
91        counter = self.askPrinterPageCounter(self.filter.printerhostname)
92        self.filter.logdebug("Printer's internal page counter value is : %s" % str(counter))
93        return counter   
94       
95    def beginJob(self, printer) :   
96        """Saves printer internal page counter at start of job."""
97        # save page counter before job
98        self.LastPageCounter = self.getPrinterInternalPageCounter()
99        self.fakeBeginJob()
100       
101    def fakeBeginJob(self) :   
102        """Fakes a begining of a job."""
103        self.counterbefore = self.getLastPageCounter()
104       
105    def endJob(self, printer) :   
106        """Saves printer internal page counter at end of job."""
107        # save page counter after job
108        self.LastPageCounter = self.counterafter = self.getPrinterInternalPageCounter()
109       
110    def getJobSize(self, printer) :   
111        """Returns the actual job size."""
112        if (not self.counterbefore) or (not self.counterafter) :
113            # there was a problem retrieving page counter
114            self.filter.printInfo(_("A problem occured while reading printer %s's internal page counter.") % printer.Name, "warn")
115            if printer.LastJob.Exists :
116                # if there's a previous job, use the last value from database
117                self.filter.printInfo(_("Retrieving printer %s's page counter from database instead.") % printer.Name, "warn")
118                if not self.counterbefore : 
119                    self.counterbefore = printer.LastJob.PrinterPageCounter or 0
120                if not self.counterafter :
121                    self.counterafter = printer.LastJob.PrinterPageCounter or 0
122                before = min(self.counterbefore, self.counterafter)   
123                after = max(self.counterbefore, self.counterafter)   
124                self.counterbefore = before
125                self.counterafter = after
126                if self.counterbefore == self.counterafter :
127                    self.filter.printInfo(_("Couldn't retrieve printer %s's internal page counter either before or after printing.") % printer.Name, "warn")
128                    self.filter.printInfo(_("Job's size forced to 1 page for printer %s.") % printer.Name, "warn")
129                    self.counterafter = self.counterbefore + 1
130            else :
131                self.filter.printInfo(_("No previous job in database for printer %s.") % printer.Name, "warn")
132                self.filter.printInfo(_("Job's size forced to 1 page for printer %s.") % printer.Name, "warn")
133                self.counterbefore = 0
134                self.counterafter = 1
135               
136        jobsize = (self.counterafter - self.counterbefore)   
137        if jobsize < 0 :
138            # Try to take care of HP printers
139            # Their internal page counter is saved to NVRAM
140            # only every 10 pages. If the printer was switched
141            # off then back on during the job, and that the
142            # counters difference is negative, we know
143            # the formula (we can't know if more than eleven
144            # pages were printed though) :
145            if jobsize > -10 :
146                jobsize += 10
147            else :   
148                # here we may have got a printer being replaced
149                # DURING the job. This is HIGHLY improbable !
150                self.filter.printInfo(_("Inconsistent values for printer %s's internal page counter.") % printer.Name, "warn")
151                self.filter.printInfo(_("Job's size forced to 1 page for printer %s.") % printer.Name, "warn")
152                jobsize = 1
153        return jobsize
154       
155    def askPrinterPageCounter(self, printer) :
156        """Returns the page counter from the printer via an external command.
157       
158           The external command must report the life time page number of the printer on stdout.
159        """
160        commandline = self.arguments.strip() % locals()
161        if printer is None :
162            raise PyKotaAccounterError, _("Unknown printer address in HARDWARE(%s) for printer %s") % (commandline, self.filter.printername)
163        self.filter.printInfo(_("Launching HARDWARE(%s)...") % commandline)
164        pagecounter = None
165        child = popen2.Popen4(commandline)   
166        try :
167            answer = child.fromchild.read()
168        except IOError :   
169            # we were interrupted by a signal, certainely a SIGTERM
170            # caused by the user cancelling the current job
171            try :
172                os.kill(child.pid, signal.SIGTERM)
173            except :   
174                pass # already killed ?
175            self.filter.printInfo(_("SIGTERM was sent to hardware accounter %s (pid: %s)") % (commandline, child.pid))
176        else :   
177            lines = [l.strip() for l in answer.split("\n")]
178            for i in range(len(lines)) : 
179                try :
180                    pagecounter = int(lines[i])
181                except (AttributeError, ValueError) :
182                    self.filter.printInfo(_("Line [%s] skipped in accounter's output. Trying again...") % lines[i])
183                else :   
184                    break
185        child.fromchild.close()   
186        child.tochild.close()
187        try :
188            status = child.wait()
189        except OSError, msg :   
190            self.filter.logdebug("Error while waiting for hardware accounter pid %s : %s" % (child.pid, msg))
191        else :   
192            if os.WIFEXITED(status) :
193                status = os.WEXITSTATUS(status)
194            self.filter.printInfo(_("Hardware accounter %s exit code is %s") % (self.arguments, str(status)))
195           
196        if pagecounter is None :
197            message = _("Unable to query printer %s via HARDWARE(%s)") % (printer, commandline)
198            if self.onerror == "CONTINUE" :
199                self.filter.printInfo(message, "error")
200            else :
201                raise PyKotaAccounterError, message 
202        return pagecounter       
Note: See TracBrowser for help on using the browser.