root / pykota / trunk / bin / pykota @ 1487

Revision 1483, 14.1 kB (checked in by jalet, 20 years ago)

Big code changes to completely remove the need for "requester" directives,
jsut use "hardware(... your previous requester directive's content ...)"

  • 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# -*- coding: ISO-8859-15 -*-
3
4# PyKota accounting filter
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22#
23# $Id$
24#
25# $Log$
26# Revision 1.58  2004/05/18 14:48:47  jalet
27# Big code changes to completely remove the need for "requester" directives,
28# jsut use "hardware(... your previous requester directive's content ...)"
29#
30# Revision 1.57  2004/03/15 10:47:56  jalet
31# This time the traceback formatting should be correct !
32#
33# Revision 1.56  2004/03/05 12:46:08  jalet
34# Improve tracebacks
35#
36# Revision 1.55  2004/03/05 12:31:35  jalet
37# Now should output full traceback when crashing
38#
39# Revision 1.54  2004/03/01 15:06:51  jalet
40# Pre and Post hooks should now work in the pykota filter too.
41# The pykota filter doesn't check the last user's quota anymore
42# when delayed hardware accounting is used : this will be checked
43# anyway the next time the last user will print
44#
45# Revision 1.53  2004/02/25 12:36:34  jalet
46# Avoids a database query even if caching was disabled.
47#
48# Revision 1.52  2004/01/14 15:57:54  jalet
49# Missing return caused problems with LPRng
50#
51# Revision 1.51  2004/01/12 18:20:45  jalet
52# Denied jobs weren't added to the history anymore, this is now fixed.
53#
54# Revision 1.50  2004/01/11 23:22:42  jalet
55# Major code refactoring, it's way cleaner, and now allows automated addition
56# of printers on first print.
57#
58# Revision 1.49  2004/01/08 14:10:32  jalet
59# Copyright year changed.
60#
61# Revision 1.48  2003/12/27 16:49:25  uid67467
62# Should be ok now.
63#
64# Revision 1.47  2003/11/26 19:17:35  jalet
65# Printing on a printer not present in the Quota Storage now results
66# in the job being stopped or cancelled depending on the system.
67#
68# Revision 1.46  2003/11/24 14:25:02  jalet
69# Missing import in pykota filter
70#
71# Revision 1.45  2003/11/19 23:19:37  jalet
72# Code refactoring work.
73# Explicit redirection to /dev/null has to be set in external policy now, just
74# like in external mailto.
75#
76# Revision 1.44  2003/11/08 16:05:31  jalet
77# CUPS backend added for people to experiment.
78#
79# Revision 1.43  2003/11/06 22:33:25  jalet
80# French variable name
81#
82# Revision 1.42  2003/10/08 21:41:38  jalet
83# External policies for printers works !
84# We can now auto-add users on first print, and do other useful things if needed.
85#
86# Revision 1.41  2003/10/07 09:07:27  jalet
87# Character encoding added to please latest version of Python
88#
89# Revision 1.40  2003/09/04 08:38:56  jalet
90# Added an exception catch to ensure clean close of database even in
91# case of TypeError too.
92#
93# Revision 1.39  2003/08/18 16:35:28  jalet
94# New pychecker pass, on the tools this time.
95#
96# Revision 1.38  2003/08/18 16:20:59  jalet
97# Improvement of the printing system detection code.
98#
99# Revision 1.37  2003/07/29 20:55:17  jalet
100# 1.14 is out !
101#
102# Revision 1.36  2003/07/10 06:09:52  jalet
103# Incorrect documentation string
104#
105# Revision 1.35  2003/06/25 14:10:01  jalet
106# Hey, it may work (edpykota --reset excepted) !
107#
108# Revision 1.34  2003/06/13 18:54:17  jalet
109# Bug with remote jobs and LPRng fixed.
110#
111# Revision 1.33  2003/05/28 13:51:38  jalet
112# Better handling of errors
113#
114# Revision 1.32  2003/05/27 23:00:20  jalet
115# Big rewrite of external accounting methods.
116# Should work well now.
117#
118# Revision 1.31  2003/04/30 13:36:39  jalet
119# Stupid accounting method was added.
120#
121# Revision 1.30  2003/04/29 22:03:38  jalet
122# Better error handling.
123#
124# Revision 1.29  2003/04/29 18:37:54  jalet
125# Pluggable accounting methods (actually doesn't support external scripts)
126#
127# Revision 1.28  2003/04/26 08:41:24  jalet
128# Small code reorganisation (UNTESTED) to allow pluggable accounting
129# methods in the future.
130#
131# Revision 1.27  2003/04/25 09:23:47  jalet
132# Debug message passed through !
133#
134# Revision 1.26  2003/04/25 08:23:23  jalet
135# Multiple tries to get the printer's internal page counter, waits for
136# one minute maximum for the printer to warm up, actually.
137#
138# Revision 1.25  2003/04/24 11:53:48  jalet
139# Default policy for unknown users/groups is to DENY printing instead
140# of the previous default to ALLOW printing. This is to solve an accuracy
141# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
142# (from PyKota's POV) will be charged to the next user who prints on the
143# same printer.
144#
145# Revision 1.24  2003/04/23 22:13:56  jalet
146# Preliminary support for LPRng added BUT STILL UNTESTED.
147#
148# Revision 1.23  2003/04/15 11:30:57  jalet
149# More work done on money print charging.
150# Minor bugs corrected.
151# All tools now access to the storage as priviledged users, repykota excepted.
152#
153# Revision 1.22  2003/04/15 11:09:04  jalet
154# Small bug was fixed when a printer was never used and its internal
155# page counter is not accessible.
156#
157# Revision 1.21  2003/04/12 17:20:14  jalet
158# Better formula for HP workaround
159#
160# Revision 1.20  2003/04/12 16:58:28  jalet
161# The workaround for HP printers was not correct, and there's probably no
162# correct way to workaround the problem because we can't save the internal
163# page counter in real time. The last job's size is unconditionnally set to
164# 5 pages in this case.
165#
166# Revision 1.19  2003/04/11 08:56:49  jalet
167# Comment
168#
169# Revision 1.18  2003/04/11 08:50:39  jalet
170# Workaround for the HP "feature" of saving the page counter to NVRAM
171# only every time 10 new pages are printed...
172# Workaround for printers with volatile page counters.
173#
174# Revision 1.17  2003/04/10 21:47:20  jalet
175# Job history added. Upgrade script neutralized for now !
176#
177# Revision 1.16  2003/04/08 20:38:08  jalet
178# The last job Id is saved now for each printer, this will probably
179# allow other accounting methods in the future.
180#
181# Revision 1.15  2003/03/29 13:45:27  jalet
182# GPL paragraphs were incorrectly (from memory) copied into the sources.
183# Two README files were added.
184# Upgrade script for PostgreSQL pre 1.01 schema was added.
185#
186# Revision 1.14  2003/03/07 22:16:57  jalet
187# Algorithmically incorrect : last user quota wasn't updated if current
188# user wasn't allowed to print.
189#
190# Revision 1.13  2003/02/27 23:59:28  jalet
191# Stupid bug wrt exception handlingand value conversion
192#
193# Revision 1.12  2003/02/27 23:48:41  jalet
194# Correctly maps PyKota's log levels to syslog log levels
195#
196# Revision 1.11  2003/02/27 22:55:20  jalet
197# WARN log priority doesn't exist.
198#
199# Revision 1.10  2003/02/27 22:43:21  jalet
200# Missing import
201#
202# Revision 1.9  2003/02/27 22:40:26  jalet
203# Correctly handles cases where the printer is off.
204#
205# Revision 1.8  2003/02/09 12:56:53  jalet
206# Internationalization begins...
207#
208# Revision 1.7  2003/02/07 10:23:48  jalet
209# Avoid a possible future name clash
210#
211# Revision 1.6  2003/02/06 22:54:33  jalet
212# warnpykota should be ok
213#
214# Revision 1.5  2003/02/05 22:45:25  jalet
215# Forgotten import
216#
217# Revision 1.4  2003/02/05 22:42:51  jalet
218# Typo
219#
220# Revision 1.3  2003/02/05 22:38:39  jalet
221# Typo
222#
223# Revision 1.2  2003/02/05 22:16:20  jalet
224# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
225#
226# Revision 1.1  2003/02/05 21:28:17  jalet
227# Initial import into CVS
228#
229#
230#
231
232import sys
233import os
234
235from pykota.tool import PyKotaFilterOrBackend, PyKotaToolError
236from pykota.config import PyKotaConfigError
237from pykota.storage import PyKotaStorageError
238from pykota.accounter import PyKotaAccounterError
239
240class PyKotaFilter(PyKotaFilterOrBackend) :       
241    """A class for the pykota filter."""
242    def acceptJob(self) :       
243        """Returns the exit code needed by the printing backend to accept the job and print it."""
244        if self.printingsystem == "CUPS" :
245            return 0
246        elif self.printingsystem == "LPRNG" :   
247            return 0
248        else :   
249            # UNKNOWN
250            return -1
251           
252    def removeJob(self) :           
253        """Returns the exit code needed by the printing backend to refuse the job and remove it."""
254        if self.printingsystem == "CUPS" :
255            return 1
256        elif self.printingsystem == "LPRNG" :   
257            return 3
258        else :   
259            # UNKNOWN
260            return -1
261       
262    def doWork(self, policy, printer, user, userpquota) :   
263        """Most of the work is done here."""
264        # Two different values possible for policy here :
265        # ALLOW means : Either printer, user or user print quota doesn't exist,
266        #               but the job should be allowed anyway.
267        # OK means : Both printer, user and user print quota exist, job should
268        #            be allowed if current user is allowed to print on this printer
269        if policy == "OK" :
270            # exports user information with initial values
271            self.exportUserInfo(userpquota)
272           
273            # enters first phase
274            os.putenv("PYKOTAPHASE", "BEFORE")
275           
276            self.logdebug("Does accounting for user %s on printer %s." % (user.Name, printer.Name))
277            action = self.accounter.doAccounting(userpquota)
278           
279            # exports some new environment variables
280            os.putenv("PYKOTAACTION", action)
281           
282            # launches the pre hook
283            self.prehook(userpquota)
284        else :       
285            action = "ALLOW"
286           
287        # pass the job's data to the next filter
288        if action in ["ALLOW", "WARN"] :
289            self.logdebug("Passing input data to next filter.")
290            mustclose = 0   
291            if self.inputfile is not None :   
292                if hasattr(self.inputfile, "read") :
293                    infile = self.inputfile
294                else :   
295                    infile = open(self.inputfile, "rb")
296                mustclose = 1
297            else :   
298                infile = sys.stdin
299            data = infile.read(256*1024)   
300            while data :
301                sys.stdout.write(data)
302                data = infile.read(256*1024)
303            if mustclose :   
304                infile.close()
305            return self.acceptJob()
306        else :
307            self.logdebug("Printing is denied.")
308            return self.removeJob()
309       
310    def mainWork(self) :   
311        # If this is a CUPS filter, we should act and die like a CUPS filter when needed
312        if self.printingsystem == "CUPS" :
313            if len(sys.argv) not in (6, 7) :   
314                sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
315                return self.removeJob()
316               
317        if self.accounter.isDelayed :       
318            # Here we need to update the last user's print quota       
319            # because hardware accounting is delayed by one print job
320            # in the pykota filter.
321            printer = self.storage.getPrinter(self.printername)       
322            if not printer.Exists :
323                self.logger.log_message(_("Printer %s not registered in the PyKota system") % self.printername, "info")
324            else :   
325                if not printer.LastJob.Exists :
326                    self.logger.log_message(_("Printer %s was never used") % self.printername, "info")
327                else :
328                    self.logdebug("Updating print quota for last user %s on printer %s" % (printer.LastJob.User.Name, printer.Name))
329                    lastuserpquota = self.storage.getUserPQuota(printer.LastJob.User, printer)
330                    self.accounter.counterbefore = printer.LastJob.PrinterPageCounter
331                    self.accounter.counterafter = self.accounter.getPrinterInternalPageCounter() or 0
332                    lastjobsize = self.accounter.getJobSize()
333                    self.logdebug("Last Job size : %i" % lastjobsize)
334                    lastjobprice = printer.LastJob.setSize(lastuserpquota, lastjobsize)
335                    self.logdebug("Updating user %s's quota on printer %s" % (lastuserpquota.User.Name, printer.Name))
336                    lastuserpquota.increasePagesUsage(lastjobsize)
337                   
338                    # exports user information with final values
339                    self.exportUserInfo(lastuserpquota)
340                   
341                    # enters final phase
342                    os.putenv("PYKOTAPHASE", "AFTER")
343                    os.putenv("PYKOTAACTION", printer.LastJob.JobAction)
344                    os.putenv("PYKOTAJOBSIZE", str(lastjobsize))
345                    os.putenv("PYKOTAJOBPRICE", str(lastjobprice))
346                   
347                    # launches the post hook
348                    self.posthook(lastuserpquota)
349                   
350                    # Code below deactivated since this will be checked
351                    # anyway the next time this user prints
352                    # finally check last user's quota
353                    # self.warnUserPQuota(lastuserpquota)
354                   
355        # then deal with current print job as usual           
356        return PyKotaFilterOrBackend.mainWork(self)       
357           
358if __name__ == "__main__" :   
359    retcode = -1
360    try :
361        # Initializes the current tool
362        kotafilter = PyKotaFilter()   
363        retcode = kotafilter.mainWork()
364    except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
365        import traceback
366        mm = [((f.endswith('\n') and f) or (f + '\n')) for f in traceback.format_exception(*sys.exc_info())]
367        sys.stderr.write("ERROR : pykota filter failed (%s)\n%s" % (msg, "ERROR : ".join(mm)))
368        sys.stderr.flush()
369        try :
370            retcode = kotafilter.removeJob()
371        except :
372            retcode = -1
373
374    try :
375        kotafilter.storage.close()
376    except (TypeError, NameError, AttributeError) :   
377        pass
378       
379    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.