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

Revision 1715, 9.1 kB (checked in by jalet, 20 years ago)

Minor fix

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