root / pykota / trunk / bin / pykota @ 966

Revision 966, 12.7 kB (checked in by jalet, 21 years ago)

Small code reorganisation (UNTESTED) to allow pluggable accounting
methods in the future.

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