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

Revision 1714, 9.0 kB (checked in by jalet, 20 years ago)

Small test added

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