root / pykota / trunk / bin / pykota @ 963

Revision 963, 12.5 kB (checked in by jalet, 21 years ago)

Debug message passed through !

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2
3# PyKota accounting filter
4#
5# PyKota - Print Quotas for CUPS and LPRng
6#
7# (c) 2003 Jerome Alet <alet@librelogiciel.com>
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21#
22# $Id$
23#
24# $Log$
25# Revision 1.27  2003/04/25 09:23:47  jalet
26# Debug message passed through !
27#
28# Revision 1.26  2003/04/25 08:23:23  jalet
29# Multiple tries to get the printer's internal page counter, waits for
30# one minute maximum for the printer to warm up, actually.
31#
32# Revision 1.25  2003/04/24 11:53:48  jalet
33# Default policy for unknown users/groups is to DENY printing instead
34# of the previous default to ALLOW printing. This is to solve an accuracy
35# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
36# (from PyKota's POV) will be charged to the next user who prints on the
37# same printer.
38#
39# Revision 1.24  2003/04/23 22:13:56  jalet
40# Preliminary support for LPRng added BUT STILL UNTESTED.
41#
42# Revision 1.23  2003/04/15 11:30:57  jalet
43# More work done on money print charging.
44# Minor bugs corrected.
45# All tools now access to the storage as priviledged users, repykota excepted.
46#
47# Revision 1.22  2003/04/15 11:09:04  jalet
48# Small bug was fixed when a printer was never used and its internal
49# page counter is not accessible.
50#
51# Revision 1.21  2003/04/12 17:20:14  jalet
52# Better formula for HP workaround
53#
54# Revision 1.20  2003/04/12 16:58:28  jalet
55# The workaround for HP printers was not correct, and there's probably no
56# correct way to workaround the problem because we can't save the internal
57# page counter in real time. The last job's size is unconditionnally set to
58# 5 pages in this case.
59#
60# Revision 1.19  2003/04/11 08:56:49  jalet
61# Comment
62#
63# Revision 1.18  2003/04/11 08:50:39  jalet
64# Workaround for the HP "feature" of saving the page counter to NVRAM
65# only every time 10 new pages are printed...
66# Workaround for printers with volatile page counters.
67#
68# Revision 1.17  2003/04/10 21:47:20  jalet
69# Job history added. Upgrade script neutralized for now !
70#
71# Revision 1.16  2003/04/08 20:38:08  jalet
72# The last job Id is saved now for each printer, this will probably
73# allow other accounting methods in the future.
74#
75# Revision 1.15  2003/03/29 13:45:27  jalet
76# GPL paragraphs were incorrectly (from memory) copied into the sources.
77# Two README files were added.
78# Upgrade script for PostgreSQL pre 1.01 schema was added.
79#
80# Revision 1.14  2003/03/07 22:16:57  jalet
81# Algorithmically incorrect : last user quota wasn't updated if current
82# user wasn't allowed to print.
83#
84# Revision 1.13  2003/02/27 23:59:28  jalet
85# Stupid bug wrt exception handlingand value conversion
86#
87# Revision 1.12  2003/02/27 23:48:41  jalet
88# Correctly maps PyKota's log levels to syslog log levels
89#
90# Revision 1.11  2003/02/27 22:55:20  jalet
91# WARN log priority doesn't exist.
92#
93# Revision 1.10  2003/02/27 22:43:21  jalet
94# Missing import
95#
96# Revision 1.9  2003/02/27 22:40:26  jalet
97# Correctly handles cases where the printer is off.
98#
99# Revision 1.8  2003/02/09 12:56:53  jalet
100# Internationalization begins...
101#
102# Revision 1.7  2003/02/07 10:23:48  jalet
103# Avoid a possible future name clash
104#
105# Revision 1.6  2003/02/06 22:54:33  jalet
106# warnpykota should be ok
107#
108# Revision 1.5  2003/02/05 22:45:25  jalet
109# Forgotten import
110#
111# Revision 1.4  2003/02/05 22:42:51  jalet
112# Typo
113#
114# Revision 1.3  2003/02/05 22:38:39  jalet
115# Typo
116#
117# Revision 1.2  2003/02/05 22:16:20  jalet
118# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
119#
120# Revision 1.1  2003/02/05 21:28:17  jalet
121# Initial import into CVS
122#
123#
124#
125
126import sys
127import os
128import time
129
130from pykota.tool import PyKotaTool, PyKotaToolError
131from pykota.requester import openRequester, PyKotaRequesterError
132
133MAXTRIES = 6     # maximum number of tries to get the printer's internal page counter
134TIMETOSLEEP = 10 # number of seconds to sleep between two tries to get the printer's internal page counter
135
136class PyKotaFilter(PyKotaTool) :   
137    """Class for the PyKota filter."""
138    def __init__(self) :
139        PyKotaTool.__init__(self)
140        (self.printingsystem, self.printerhostname, self.printername, self.username, self.jobid, self.inputfile) = self.extractInfoFromCupsOrLprng()
141        self.requester = openRequester(self.config, self.printername)
142   
143    def filterInput(self, inputfile) :
144        """Transparent filter."""
145        mustclose = 0   
146        if inputfile is not None :   
147            infile = open(inputfile, "rb")
148            mustclose = 1
149        else :   
150            infile = sys.stdin
151        data = infile.read(256*1024)   
152        while data :
153            sys.stdout.write(data)
154            data = infile.read(256*1024)
155        if mustclose :   
156            infile.close()
157           
158    def acceptJob(self) :       
159        """Returns the exit code needed by the printing backend to accept the job and print it."""
160        if self.printingsystem == "CUPS" :
161            return 0
162        elif self.printingsystem == "LPRNG" :   
163            return 0
164        else :   
165            # UNKNOWN
166            return -1
167           
168    def removeJob(self) :           
169        """Returns the exit code needed by the printing backend to refuse the job and remove it."""
170        if self.printingsystem == "CUPS" :
171            return 1
172        elif self.printingsystem == "LPRNG" :   
173            return 3
174        else :   
175            # UNKNOWN
176            return -1
177           
178def main() :   
179    """Do it, and do it right !"""
180    global MAXTRIES, TIMETOSLEEP
181   
182    # Initializes the current tool
183    kotafilter = PyKotaFilter()   
184   
185    #
186    # If this is a CUPS filter, we should act and die like a CUPS filter when needed
187    if kotafilter.printingsystem == "CUPS" :
188        if len(sys.argv) not in (6, 7) :   
189            sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
190            return kotafilter.removeJob()
191   
192    # Get the page counter directly from the printer itself
193    # Tries MAXTRIES times, sleeping two seconds each time, in case the printer is sleeping.
194    # This was seen with my Apple LaserWriter 16/600 PS which doesn't answer before having warmed up.
195    for i in range(MAXTRIES) :
196        try :
197            counterbeforejob = kotafilter.requester.getPrinterPageCounter(kotafilter.printerhostname)
198        except PyKotaRequesterError, msg :
199            # can't get actual page counter, assume printer is off or warming up
200            # log the message anyway.
201            kotafilter.logger.log_message("%s" % msg, "warn")
202            counterbeforejob = None
203            printerIsOff = 1
204        else :   
205            # printer answered, it is on so we can exit the loop
206            printerIsOff = 0
207            break
208        time.sleep(TIMETOSLEEP)   
209       
210    # Get the last page counter and last username from the Quota Storage backend
211    printerid = kotafilter.storage.getPrinterId(kotafilter.printername)
212    if printerid is None :
213        # The printer is unknown from the Quota Storage perspective
214        # we let the job pass through, but log a warning message
215        kotafilter.logger.log_message(_("Printer %s not registered in the PyKota system") % kotafilter.printername, "warn")
216    else :   
217        userid = kotafilter.storage.getUserId(kotafilter.username)
218        if userid is None :
219            # The user is unknown from the Quota Storage perspective
220            # Depending on the default policy for this printer, we
221            # either let the job pass through or reject it, but we
222            # log a message in any case.
223            policy = kotafilter.config.getPrinterPolicy(kotafilter.printername)
224            if policy == "ALLOW" :
225                action = "POLICY_ALLOW"
226            else :   
227                action = "POLICY_DENY"
228            kotafilter.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (kotafilter.username, action, kotafilter.printername), "warn")
229            if action == "POLICY_DENY" :
230                return kotafilter.removeJob()
231        else :
232            # get last job information for this printer
233            pgc = kotafilter.storage.getPrinterPageCounter(printerid)   
234            if pgc is None :
235                # The printer hasn't been used yet, from PyKota's point of view
236                lasthistoryid = None
237                lastjobid = kotafilter.jobid
238                lastuserid = userid
239                lastusername = kotafilter.username
240                lastpagecounter = counterbeforejob
241            else :   
242                # get last values from Quota Storage
243                (lasthistoryid, lastjobid, lastuserid, lastusername, lastpagecounter) = (pgc["id"], pgc["jobid"], pgc["userid"], pgc["username"], pgc["pagecounter"])
244               
245            # if printer is off then we assume the correct counter value is the last one
246            if printerIsOff :
247                counterbeforejob = lastpagecounter
248               
249            # if the internal lifetime page counter for this printer is 0   
250            # then this may be a printer with a volatile counter (never
251            # saved to NVRAM) which has just been switched off and then on
252            # so we use the last page counter from the Quota Storage instead
253            # explanation at : http://web.mit.edu/source/third/lprng/doc/LPRng-HOWTO-15.html
254            if counterbeforejob == 0 :
255                counterbeforejob = lastpagecounter
256               
257            # Computes the last job size as the difference between internal page
258            # counter in the printer and last page counter taken from the Quota
259            # Storage database for this particular printer
260            try :
261                jobsize = (counterbeforejob - lastpagecounter)   
262            except TypeError :   
263                # never used, and internal page counter not accessible
264                jobsize = 0
265               
266            if jobsize < 0 :
267                # Probably an HP printer which was switched off and back on,
268                # its primary counter is only saved in a 10 increment, so
269                # it may be lower than the last page counter saved in the
270                # Quota Storage.
271                # We unconditionnally set the last job's size to
272                # abs(int((10 - abs(lastcounter(snmp) - lastcounter(storage)) / 2))
273                # For more accurate accounting, don't switch off your HP printers !
274                # explanation at : http://web.mit.edu/source/third/lprng/doc/LPRng-HOWTO-15.html
275                kotafilter.logger.log_message(_("Error in page count value %i for user %s on printer %s") % (jobsize, lastusername, kotafilter.printername), "error")
276                jobsize = abs(int((10 - abs(jobsize)) / 2))     # Workaround for HP printers' feature !
277               
278            # update the quota for the previous user on this printer
279            kotafilter.storage.updateUserPQuota(lastuserid, printerid, jobsize)
280           
281            # update the last job size in the history
282            kotafilter.storage.updateJobSizeInHistory(lasthistoryid, jobsize)
283           
284            # warns the last user if he is over quota
285            kotafilter.warnUserPQuota(lastusername, kotafilter.printername)
286               
287            # Is the current user allowed to print at all ?
288            action = kotafilter.warnUserPQuota(kotafilter.username, kotafilter.printername)
289           
290            # adds the current job to history   
291            kotafilter.storage.addJobToHistory(kotafilter.jobid, kotafilter.storage.getUserId(kotafilter.username), printerid, counterbeforejob, action)
292           
293            # if not allowed to print then die, else proceed.
294            if action == "DENY" :
295                # No, just die cleanly
296                return kotafilter.removeJob()
297       
298    # pass the job untouched to the underlying layer
299    kotafilter.filterInput(kotafilter.inputfile)     
300   
301    return kotafilter.acceptJob()
302
303if __name__ == "__main__" :   
304    sys.exit(main())
Note: See TracBrowser for help on using the browser.