root / pykota / trunk / bin / pykota @ 1124

Revision 1124, 11.9 kB (checked in by jalet, 21 years ago)

Added an exception catch to ensure clean close of database even in
case of TypeError? too.

  • 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.40  2003/09/04 08:38:56  jalet
26# Added an exception catch to ensure clean close of database even in
27# case of TypeError too.
28#
29# Revision 1.39  2003/08/18 16:35:28  jalet
30# New pychecker pass, on the tools this time.
31#
32# Revision 1.38  2003/08/18 16:20:59  jalet
33# Improvement of the printing system detection code.
34#
35# Revision 1.37  2003/07/29 20:55:17  jalet
36# 1.14 is out !
37#
38# Revision 1.36  2003/07/10 06:09:52  jalet
39# Incorrect documentation string
40#
41# Revision 1.35  2003/06/25 14:10:01  jalet
42# Hey, it may work (edpykota --reset excepted) !
43#
44# Revision 1.34  2003/06/13 18:54:17  jalet
45# Bug with remote jobs and LPRng fixed.
46#
47# Revision 1.33  2003/05/28 13:51:38  jalet
48# Better handling of errors
49#
50# Revision 1.32  2003/05/27 23:00:20  jalet
51# Big rewrite of external accounting methods.
52# Should work well now.
53#
54# Revision 1.31  2003/04/30 13:36:39  jalet
55# Stupid accounting method was added.
56#
57# Revision 1.30  2003/04/29 22:03:38  jalet
58# Better error handling.
59#
60# Revision 1.29  2003/04/29 18:37:54  jalet
61# Pluggable accounting methods (actually doesn't support external scripts)
62#
63# Revision 1.28  2003/04/26 08:41:24  jalet
64# Small code reorganisation (UNTESTED) to allow pluggable accounting
65# methods in the future.
66#
67# Revision 1.27  2003/04/25 09:23:47  jalet
68# Debug message passed through !
69#
70# Revision 1.26  2003/04/25 08:23:23  jalet
71# Multiple tries to get the printer's internal page counter, waits for
72# one minute maximum for the printer to warm up, actually.
73#
74# Revision 1.25  2003/04/24 11:53:48  jalet
75# Default policy for unknown users/groups is to DENY printing instead
76# of the previous default to ALLOW printing. This is to solve an accuracy
77# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
78# (from PyKota's POV) will be charged to the next user who prints on the
79# same printer.
80#
81# Revision 1.24  2003/04/23 22:13:56  jalet
82# Preliminary support for LPRng added BUT STILL UNTESTED.
83#
84# Revision 1.23  2003/04/15 11:30:57  jalet
85# More work done on money print charging.
86# Minor bugs corrected.
87# All tools now access to the storage as priviledged users, repykota excepted.
88#
89# Revision 1.22  2003/04/15 11:09:04  jalet
90# Small bug was fixed when a printer was never used and its internal
91# page counter is not accessible.
92#
93# Revision 1.21  2003/04/12 17:20:14  jalet
94# Better formula for HP workaround
95#
96# Revision 1.20  2003/04/12 16:58:28  jalet
97# The workaround for HP printers was not correct, and there's probably no
98# correct way to workaround the problem because we can't save the internal
99# page counter in real time. The last job's size is unconditionnally set to
100# 5 pages in this case.
101#
102# Revision 1.19  2003/04/11 08:56:49  jalet
103# Comment
104#
105# Revision 1.18  2003/04/11 08:50:39  jalet
106# Workaround for the HP "feature" of saving the page counter to NVRAM
107# only every time 10 new pages are printed...
108# Workaround for printers with volatile page counters.
109#
110# Revision 1.17  2003/04/10 21:47:20  jalet
111# Job history added. Upgrade script neutralized for now !
112#
113# Revision 1.16  2003/04/08 20:38:08  jalet
114# The last job Id is saved now for each printer, this will probably
115# allow other accounting methods in the future.
116#
117# Revision 1.15  2003/03/29 13:45:27  jalet
118# GPL paragraphs were incorrectly (from memory) copied into the sources.
119# Two README files were added.
120# Upgrade script for PostgreSQL pre 1.01 schema was added.
121#
122# Revision 1.14  2003/03/07 22:16:57  jalet
123# Algorithmically incorrect : last user quota wasn't updated if current
124# user wasn't allowed to print.
125#
126# Revision 1.13  2003/02/27 23:59:28  jalet
127# Stupid bug wrt exception handlingand value conversion
128#
129# Revision 1.12  2003/02/27 23:48:41  jalet
130# Correctly maps PyKota's log levels to syslog log levels
131#
132# Revision 1.11  2003/02/27 22:55:20  jalet
133# WARN log priority doesn't exist.
134#
135# Revision 1.10  2003/02/27 22:43:21  jalet
136# Missing import
137#
138# Revision 1.9  2003/02/27 22:40:26  jalet
139# Correctly handles cases where the printer is off.
140#
141# Revision 1.8  2003/02/09 12:56:53  jalet
142# Internationalization begins...
143#
144# Revision 1.7  2003/02/07 10:23:48  jalet
145# Avoid a possible future name clash
146#
147# Revision 1.6  2003/02/06 22:54:33  jalet
148# warnpykota should be ok
149#
150# Revision 1.5  2003/02/05 22:45:25  jalet
151# Forgotten import
152#
153# Revision 1.4  2003/02/05 22:42:51  jalet
154# Typo
155#
156# Revision 1.3  2003/02/05 22:38:39  jalet
157# Typo
158#
159# Revision 1.2  2003/02/05 22:16:20  jalet
160# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
161#
162# Revision 1.1  2003/02/05 21:28:17  jalet
163# Initial import into CVS
164#
165#
166#
167
168import sys
169import os
170
171from pykota.tool import PyKotaTool, PyKotaToolError
172from pykota.config import PyKotaConfigError
173from pykota.storage import PyKotaStorageError
174from pykota.accounter import openAccounter, PyKotaAccounterError
175
176class PyKotaFilter(PyKotaTool) :   
177    """Class for the PyKota filter."""
178    def __init__(self) :
179        PyKotaTool.__init__(self)
180        (self.printingsystem, self.printerhostname, self.printername, self.username, self.jobid, self.inputfile, self.copies) = self.extractInfoFromCupsOrLprng()
181        self.accounter = openAccounter(self)
182   
183    def extractInfoFromCupsOrLprng(self) :   
184        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename) depending on the printing system in use (as seen by the print filter).
185       
186           Returns (None, None, None, None, None, None, None) if no printing system is recognized.
187        """
188        # Try to detect CUPS
189        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
190            if len(sys.argv) == 7 :
191                inputfile = sys.argv[6]
192            else :   
193                inputfile = None
194               
195            device_uri = os.environ.get("DEVICE_URI", "")
196            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
197            try :
198                (backend, destination) = device_uri.split(":", 1) 
199            except ValueError :   
200                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
201            while destination.startswith("/") :
202                destination = destination[1:]
203            printerhostname = destination.split("/")[0].split(":")[0]
204            return ("CUPS", printerhostname, os.environ.get("PRINTER"), sys.argv[2].strip(), sys.argv[1].strip(), inputfile, int(sys.argv[4].strip()))
205        else :   
206            # Try to detect LPRng
207            jseen = Pseen = nseen = rseen = Kseen = None
208            for arg in sys.argv :
209                if arg.startswith("-j") :
210                    jseen = arg[2:].strip()
211                elif arg.startswith("-n") :     
212                    nseen = arg[2:].strip()
213                elif arg.startswith("-P") :   
214                    Pseen = arg[2:].strip()
215                elif arg.startswith("-r") :   
216                    rseen = arg[2:].strip()
217                elif arg.startswith("-K") or arg.startswith("-#") :   
218                    Kseen = int(arg[2:].strip())
219            if Kseen is None :       
220                Kseen = 1       # we assume the user wants at least one copy...
221            if (rseen is None) and jseen and Pseen and nseen :   
222                self.logger.log_message(_("Printer hostname undefined, set to 'localhost'"), "warn")
223                rseen = "localhost"
224            if jseen and Pseen and nseen and rseen :       
225                # job is always in stdin (None)
226                return ("LPRNG", rseen, Pseen, nseen, jseen, None, Kseen)
227        self.logger.log_message(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
228        return (None, None, None, None, None, None, None)   # Unknown printing system         
229       
230    def acceptJob(self) :       
231        """Returns the exit code needed by the printing backend to accept the job and print it."""
232        if self.printingsystem == "CUPS" :
233            return 0
234        elif self.printingsystem == "LPRNG" :   
235            return 0
236        else :   
237            # UNKNOWN
238            return -1
239           
240    def removeJob(self) :           
241        """Returns the exit code needed by the printing backend to refuse the job and remove it."""
242        if self.printingsystem == "CUPS" :
243            return 1
244        elif self.printingsystem == "LPRNG" :   
245            return 3
246        else :   
247            # UNKNOWN
248            return -1
249           
250def main(thefilter) :   
251    """Do it, and do it right !"""
252    #
253    # If this is a CUPS filter, we should act and die like a CUPS filter when needed
254    if thefilter.printingsystem == "CUPS" :
255        if len(sys.argv) not in (6, 7) :   
256            sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
257            return thefilter.removeJob()
258   
259    # Get the last page counter and last username from the Quota Storage backend
260    printer = thefilter.storage.getPrinter(thefilter.printername)
261    if not printer.Exists :
262        # The printer is unknown from the Quota Storage perspective
263        # we let the job pass through, but log a warning message
264        thefilter.logger.log_message(_("Printer %s not registered in the PyKota system") % thefilter.printername, "warn")
265    else :   
266        user = thefilter.storage.getUser(thefilter.username)
267        if not user.Exists :
268            # The user is unknown from the Quota Storage perspective
269            # Depending on the default policy for this printer, we
270            # either let the job pass through or reject it, but we
271            # log a message in any case.
272            policy = thefilter.config.getPrinterPolicy(thefilter.printername)
273            if policy == "ALLOW" :
274                action = "POLICY_ALLOW"
275            else :   
276                action = "POLICY_DENY"
277            thefilter.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thefilter.username, action, thefilter.printername), "warn")
278            if action == "POLICY_DENY" :
279                return thefilter.removeJob()
280        else :
281            # Now does the accounting and act depending on the result
282            action = thefilter.accounter.doAccounting(printer, user)
283           
284            # if not allowed to print then die, else proceed.
285            if action == "DENY" :
286                # No, just die cleanly
287                return thefilter.removeJob()
288       
289    # pass the job untouched to the underlying layer
290    thefilter.accounter.filterInput(thefilter.inputfile)     
291   
292    return thefilter.acceptJob()
293
294if __name__ == "__main__" :   
295    retcode = -1
296    try :
297        # Initializes the current tool
298        kotafilter = PyKotaFilter()   
299        retcode = main(kotafilter)
300    except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
301        sys.stderr.write("ERROR : PyKota filter failed (%s)\n" % msg)
302        sys.stderr.flush()
303        retcode = -1
304
305    try :
306        kotafilter.storage.close()
307    except (TypeError, NameError, AttributeError) :   
308        pass
309       
310    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.