root / pykota / trunk / pykota / tool.py @ 1637

Revision 1637, 51.6 kB (checked in by jalet, 20 years ago)

Unitialized variable

  • 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.116  2004/07/24 20:20:29  jalet
25# Unitialized variable
26#
27# Revision 1.115  2004/07/21 09:35:48  jalet
28# Software accounting seems to be OK with LPRng support now
29#
30# Revision 1.114  2004/07/20 22:19:45  jalet
31# Sanitized a bit + use of gettext
32#
33# Revision 1.113  2004/07/17 20:37:27  jalet
34# Missing file... Am I really stupid ?
35#
36# Revision 1.112  2004/07/16 12:22:47  jalet
37# LPRng support early version
38#
39# Revision 1.111  2004/07/06 18:09:42  jalet
40# Reduced the set of invalid characters in names
41#
42# Revision 1.110  2004/07/01 19:56:42  jalet
43# Better dispatching of error messages
44#
45# Revision 1.109  2004/07/01 17:45:49  jalet
46# Added code to handle the description field for printers
47#
48# Revision 1.108  2004/06/24 23:09:30  jalet
49# Also prints read size on last block
50#
51# Revision 1.107  2004/06/23 13:03:28  jalet
52# Catches accounter configuration errors earlier
53#
54# Revision 1.106  2004/06/22 09:31:18  jalet
55# Always send some debug info to CUPS' back channel stream (stderr) as
56# informationnal messages.
57#
58# Revision 1.105  2004/06/21 08:17:38  jalet
59# Added version number in subject message for directive crashrecipient.
60#
61# Revision 1.104  2004/06/18 13:34:49  jalet
62# Now all tracebacks include PyKota's version number
63#
64# Revision 1.103  2004/06/18 13:17:26  jalet
65# Now includes PyKota's version number in messages sent by the crashrecipient
66# directive.
67#
68# Revision 1.102  2004/06/17 13:26:51  jalet
69# Better exception handling code
70#
71# Revision 1.101  2004/06/16 20:56:34  jalet
72# Smarter initialisation code
73#
74# Revision 1.100  2004/06/11 08:16:03  jalet
75# More exceptions catched in case of very early failure.
76#
77# Revision 1.99  2004/06/11 07:07:38  jalet
78# Now detects and logs configuration syntax errors instead of failing without
79# any notice message.
80#
81# Revision 1.98  2004/06/08 19:27:12  jalet
82# Doesn't ignore SIGCHLD anymore
83#
84# Revision 1.97  2004/06/07 22:45:35  jalet
85# Now accepts a job when enforcement is STRICT and predicted account balance
86# is equal to 0.0 : since the job hasn't been printed yet, only its printing
87# will really render balance equal to 0.0, so we should be allowed to print.
88#
89# Revision 1.96  2004/06/05 22:18:04  jalet
90# Now catches some exceptions earlier.
91# storage.py and ldapstorage.py : removed old comments
92#
93# Revision 1.95  2004/06/03 21:50:34  jalet
94# Improved error logging.
95# crashrecipient directive added.
96# Now exports the job's size in bytes too.
97#
98# Revision 1.94  2004/06/03 08:51:03  jalet
99# logs job's size in bytes now
100#
101# Revision 1.93  2004/06/02 21:51:02  jalet
102# Moved the sigterm capturing elsewhere
103#
104# Revision 1.92  2004/06/02 13:21:38  jalet
105# Debug message added
106#
107# Revision 1.91  2004/05/25 05:17:52  jalet
108# Now precomputes the job's size only if current printer's enforcement
109# is "STRICT"
110#
111# Revision 1.90  2004/05/24 22:45:49  jalet
112# New 'enforcement' directive added
113# Polling loop improvements
114#
115# Revision 1.89  2004/05/21 22:02:52  jalet
116# Preliminary work on pre-accounting
117#
118# Revision 1.88  2004/05/18 14:49:20  jalet
119# Big code changes to completely remove the need for "requester" directives,
120# jsut use "hardware(... your previous requester directive's content ...)"
121#
122# Revision 1.87  2004/05/17 19:14:59  jalet
123# Now catches SIGPIPE and SIGCHLD
124#
125# Revision 1.86  2004/05/13 13:59:28  jalet
126# Code simplifications
127#
128# Revision 1.85  2004/05/11 08:26:27  jalet
129# Now catches connection problems to SMTP server
130#
131# Revision 1.84  2004/04/21 08:36:32  jalet
132# Exports the PYKOTASTATUS environment variable when SIGTERM is received.
133#
134# Revision 1.83  2004/04/16 17:03:49  jalet
135# The list of printers groups the current printer is a member of is
136# now exported in the PYKOTAPGROUPS environment variable
137#
138# Revision 1.82  2004/04/13 09:38:03  jalet
139# More work on correct child processes handling
140#
141# Revision 1.81  2004/04/09 22:24:47  jalet
142# Began work on correct handling of child processes when jobs are cancelled by
143# the user. Especially important when an external requester is running for a
144# long time.
145#
146# Revision 1.80  2004/04/06 12:00:21  jalet
147# uninitialized values caused problems
148#
149# Revision 1.79  2004/03/28 21:01:29  jalet
150# PYKOTALIMITBY environment variable is now exported too
151#
152# Revision 1.78  2004/03/08 20:13:25  jalet
153# Allow names to begin with a digit
154#
155# Revision 1.77  2004/03/03 13:10:35  jalet
156# Now catches all smtplib exceptions when there's a problem sending messages
157#
158# Revision 1.76  2004/03/01 14:34:15  jalet
159# PYKOTAPHASE wasn't set at the right time at the end of data transmission
160# to underlying layer (real backend)
161#
162# Revision 1.75  2004/03/01 11:23:25  jalet
163# Pre and Post hooks to external commands are available in the cupspykota
164# backend. Forthe pykota filter they will be implemented real soon now.
165#
166# Revision 1.74  2004/02/26 14:18:07  jalet
167# Should fix the remaining bugs wrt printers groups and users groups.
168#
169# Revision 1.73  2004/02/19 14:20:21  jalet
170# maildomain pykota.conf directive added.
171# Small improvements on mail headers quality.
172#
173# Revision 1.72  2004/01/14 15:51:19  jalet
174# Docstring added.
175#
176# Revision 1.71  2004/01/11 23:22:42  jalet
177# Major code refactoring, it's way cleaner, and now allows automated addition
178# of printers on first print.
179#
180# Revision 1.70  2004/01/08 14:10:32  jalet
181# Copyright year changed.
182#
183# Revision 1.69  2004/01/05 16:02:18  jalet
184# Dots in user, groups and printer names should be allowed.
185#
186# Revision 1.68  2004/01/02 17:38:40  jalet
187# This time it should be better...
188#
189# Revision 1.67  2004/01/02 17:37:09  jalet
190# I'm completely stupid !!! Better to not talk while coding !
191#
192# Revision 1.66  2004/01/02 17:31:26  jalet
193# Forgot to remove some code
194#
195# Revision 1.65  2003/12/06 08:14:38  jalet
196# Added support for CUPS device uris which contain authentication information.
197#
198# Revision 1.64  2003/11/29 22:03:17  jalet
199# Some code refactoring work. New code is not used at this time.
200#
201# Revision 1.63  2003/11/29 20:06:20  jalet
202# Added 'utolower' configuration option to convert all usernames to
203# lowercase when printing. All database accesses are still and will
204# remain case sensitive though.
205#
206# Revision 1.62  2003/11/25 22:37:22  jalet
207# Small code move
208#
209# Revision 1.61  2003/11/25 22:03:28  jalet
210# No more message on stderr when the translation is not available.
211#
212# Revision 1.60  2003/11/25 21:54:05  jalet
213# updated FAQ
214#
215# Revision 1.59  2003/11/25 13:33:43  jalet
216# Puts 'root' instead of '' when printing from CUPS web interface (which
217# gives an empty username)
218#
219# Revision 1.58  2003/11/21 14:28:45  jalet
220# More complete job history.
221#
222# Revision 1.57  2003/11/19 23:19:38  jalet
223# Code refactoring work.
224# Explicit redirection to /dev/null has to be set in external policy now, just
225# like in external mailto.
226#
227# Revision 1.56  2003/11/19 07:40:20  jalet
228# Missing import statement.
229# Better documentation for mailto: external(...)
230#
231# Revision 1.55  2003/11/18 23:43:12  jalet
232# Mailto can be any external command now, as usual.
233#
234# Revision 1.54  2003/10/24 21:52:46  jalet
235# Now can force language when coming from CGI script.
236#
237# Revision 1.53  2003/10/08 21:41:38  jalet
238# External policies for printers works !
239# We can now auto-add users on first print, and do other useful things if needed.
240#
241# Revision 1.52  2003/10/07 09:07:28  jalet
242# Character encoding added to please latest version of Python
243#
244# Revision 1.51  2003/10/06 14:21:41  jalet
245# Test reversed to not retrieve group members when no messages for them.
246#
247# Revision 1.50  2003/10/02 20:23:18  jalet
248# Storage caching mechanism added.
249#
250# Revision 1.49  2003/07/29 20:55:17  jalet
251# 1.14 is out !
252#
253# Revision 1.48  2003/07/21 23:01:56  jalet
254# Modified some messages aout soft limit
255#
256# Revision 1.47  2003/07/16 21:53:08  jalet
257# Really big modifications wrt new configuration file's location and content.
258#
259# Revision 1.46  2003/07/09 20:17:07  jalet
260# Email field added to PostgreSQL schema
261#
262# Revision 1.45  2003/07/08 19:43:51  jalet
263# Configurable warning messages.
264# Poor man's treshold value added.
265#
266# Revision 1.44  2003/07/07 11:49:24  jalet
267# Lots of small fixes with the help of PyChecker
268#
269# Revision 1.43  2003/07/04 09:06:32  jalet
270# Small bug fix wrt undefined "LimitBy" field.
271#
272# Revision 1.42  2003/06/30 12:46:15  jalet
273# Extracted reporting code.
274#
275# Revision 1.41  2003/06/25 14:10:01  jalet
276# Hey, it may work (edpykota --reset excepted) !
277#
278# Revision 1.40  2003/06/10 16:37:54  jalet
279# Deletion of the second user which is not needed anymore.
280# Added a debug configuration field in /etc/pykota.conf
281# All queries can now be sent to the logger in debug mode, this will
282# greatly help improve performance when time for this will come.
283#
284# Revision 1.39  2003/04/29 18:37:54  jalet
285# Pluggable accounting methods (actually doesn't support external scripts)
286#
287# Revision 1.38  2003/04/24 11:53:48  jalet
288# Default policy for unknown users/groups is to DENY printing instead
289# of the previous default to ALLOW printing. This is to solve an accuracy
290# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
291# (from PyKota's POV) will be charged to the next user who prints on the
292# same printer.
293#
294# Revision 1.37  2003/04/24 08:08:27  jalet
295# Debug message forgotten
296#
297# Revision 1.36  2003/04/24 07:59:40  jalet
298# LPRng support now works !
299#
300# Revision 1.35  2003/04/23 22:13:57  jalet
301# Preliminary support for LPRng added BUT STILL UNTESTED.
302#
303# Revision 1.34  2003/04/17 09:26:21  jalet
304# repykota now reports account balances too.
305#
306# Revision 1.33  2003/04/16 12:35:49  jalet
307# Groups quota work now !
308#
309# Revision 1.32  2003/04/16 08:53:14  jalet
310# Printing can now be limited either by user's account balance or by
311# page quota (the default). Quota report doesn't include account balance
312# yet, though.
313#
314# Revision 1.31  2003/04/15 11:30:57  jalet
315# More work done on money print charging.
316# Minor bugs corrected.
317# All tools now access to the storage as priviledged users, repykota excepted.
318#
319# Revision 1.30  2003/04/10 21:47:20  jalet
320# Job history added. Upgrade script neutralized for now !
321#
322# Revision 1.29  2003/03/29 13:45:27  jalet
323# GPL paragraphs were incorrectly (from memory) copied into the sources.
324# Two README files were added.
325# Upgrade script for PostgreSQL pre 1.01 schema was added.
326#
327# Revision 1.28  2003/03/29 13:08:28  jalet
328# Configuration is now expected to be found in /etc/pykota.conf instead of
329# in /etc/cups/pykota.conf
330# Installation script can move old config files to the new location if needed.
331# Better error handling if configuration file is absent.
332#
333# Revision 1.27  2003/03/15 23:01:28  jalet
334# New mailto option in configuration file added.
335# No time to test this tonight (although it should work).
336#
337# Revision 1.26  2003/03/09 23:58:16  jalet
338# Comment
339#
340# Revision 1.25  2003/03/07 22:56:14  jalet
341# 0.99 is out with some bug fixes.
342#
343# Revision 1.24  2003/02/27 23:48:41  jalet
344# Correctly maps PyKota's log levels to syslog log levels
345#
346# Revision 1.23  2003/02/27 22:55:20  jalet
347# WARN log priority doesn't exist.
348#
349# Revision 1.22  2003/02/27 09:09:20  jalet
350# Added a method to match strings against wildcard patterns
351#
352# Revision 1.21  2003/02/17 23:01:56  jalet
353# Typos
354#
355# Revision 1.20  2003/02/17 22:55:01  jalet
356# More options can now be set per printer or globally :
357#
358#       admin
359#       adminmail
360#       gracedelay
361#       requester
362#
363# the printer option has priority when both are defined.
364#
365# Revision 1.19  2003/02/10 11:28:45  jalet
366# Localization
367#
368# Revision 1.18  2003/02/10 01:02:17  jalet
369# External requester is about to work, but I must sleep
370#
371# Revision 1.17  2003/02/09 13:05:43  jalet
372# Internationalization continues...
373#
374# Revision 1.16  2003/02/09 12:56:53  jalet
375# Internationalization begins...
376#
377# Revision 1.15  2003/02/08 22:09:52  jalet
378# Name check method moved here
379#
380# Revision 1.14  2003/02/07 10:42:45  jalet
381# Indentation problem
382#
383# Revision 1.13  2003/02/07 08:34:16  jalet
384# Test wrt date limit was wrong
385#
386# Revision 1.12  2003/02/06 23:20:02  jalet
387# warnpykota doesn't need any user/group name argument, mimicing the
388# warnquota disk quota tool.
389#
390# Revision 1.11  2003/02/06 22:54:33  jalet
391# warnpykota should be ok
392#
393# Revision 1.10  2003/02/06 15:03:11  jalet
394# added a method to set the limit date
395#
396# Revision 1.9  2003/02/06 10:39:23  jalet
397# Preliminary edpykota work.
398#
399# Revision 1.8  2003/02/06 09:19:02  jalet
400# More robust behavior (hopefully) when the user or printer is not managed
401# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
402# printer and/or user not 'yet?' in storage.
403#
404# Revision 1.7  2003/02/06 00:00:45  jalet
405# Now includes the printer name in email messages
406#
407# Revision 1.6  2003/02/05 23:55:02  jalet
408# Cleaner email messages
409#
410# Revision 1.5  2003/02/05 23:45:09  jalet
411# Better DateTime manipulation wrt grace delay
412#
413# Revision 1.4  2003/02/05 23:26:22  jalet
414# Incorrect handling of grace delay
415#
416# Revision 1.3  2003/02/05 22:16:20  jalet
417# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
418#
419# Revision 1.2  2003/02/05 22:10:29  jalet
420# Typos
421#
422# Revision 1.1  2003/02/05 21:28:17  jalet
423# Initial import into CVS
424#
425#
426#
427
428import sys
429import os
430import fnmatch
431import getopt
432import smtplib
433import gettext
434import locale
435import signal
436import socket
437import tempfile
438import ConfigParser
439
440from mx import DateTime
441
442from pykota import version, config, storage, logger, accounter, pdlanalyzer
443
444class PyKotaToolError(Exception):
445    """An exception for PyKota config related stuff."""
446    def __init__(self, message = ""):
447        self.message = message
448        Exception.__init__(self, message)
449    def __repr__(self):
450        return self.message
451    __str__ = __repr__
452   
453def crashed(message) :   
454    """Minimal crash method."""
455    import traceback
456    lines = []
457    for line in traceback.format_exception(*sys.exc_info()) :
458        lines.extend([l for l in line.split("\n") if l])
459    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKota v%s" % version.__version__, message] + lines)])
460    sys.stderr.write(msg)
461    sys.stderr.flush()
462    return msg
463
464class PyKotaTool :   
465    """Base class for all PyKota command line tools."""
466    def __init__(self, lang=None, doc="PyKota %s (c) 2003-2004 %s" % (version.__version__, version.__author__)) :
467        """Initializes the command line tool."""
468        # locale stuff
469        try :
470            locale.setlocale(locale.LC_ALL, lang)
471            gettext.install("pykota")
472        except (locale.Error, IOError) :
473            gettext.NullTranslations().install()
474   
475        # pykota specific stuff
476        self.documentation = doc
477        try :
478            self.config = config.PyKotaConfig("/etc/pykota")
479        except ConfigParser.ParsingError, msg :   
480            sys.stderr.write("ERROR: Problem encountered while parsing configuration file : %s\n" % msg)
481            sys.stderr.flush()
482            sys.exit(-1)
483           
484        try :
485            self.debug = self.config.getDebug()
486            self.smtpserver = self.config.getSMTPServer()
487            self.maildomain = self.config.getMailDomain()
488            self.logger = logger.openLogger(self.config.getLoggingBackend())
489            self.storage = storage.openConnection(self)
490        except (config.PyKotaConfigError, logger.PyKotaLoggingError, storage.PyKotaStorageError), msg :
491            self.crashed(msg)
492            raise
493        self.softwareJobSize = 0
494        self.softwareJobPrice = 0.0
495       
496    def logdebug(self, message) :   
497        """Logs something to debug output if debug is enabled."""
498        if self.debug :
499            self.logger.log_message(message, "debug")
500           
501    def printInfo(self, message, level="info") :       
502        """Sends a message to standard error."""
503        sys.stderr.write("%s: %s\n" % (level.upper(), message))
504        sys.stderr.flush()
505       
506    def clean(self) :   
507        """Ensures that the database is closed."""
508        try :
509            self.storage.close()
510        except (TypeError, NameError, AttributeError) :   
511            pass
512           
513    def display_version_and_quit(self) :
514        """Displays version number, then exists successfully."""
515        self.clean()
516        print version.__version__
517        sys.exit(0)
518   
519    def display_usage_and_quit(self) :
520        """Displays command line usage, then exists successfully."""
521        self.clean()
522        print self.documentation
523        sys.exit(0)
524       
525    def crashed(self, message) :   
526        """Outputs a crash message, and optionally sends it to software author."""
527        msg = crashed(message)
528        try :
529            crashrecipient = self.config.getCrashRecipient()
530            if crashrecipient :
531                admin = self.config.getAdminMail("global") # Nice trick, isn't it ?
532                fullmessage = "========== Traceback :\n\n%s\n\n========== sys.argv :\n\n%s\n\n========== Environment :\n\n%s\n" % \
533                                (msg, \
534                                 "\n".join(["    %s" % repr(a) for a in sys.argv]), \
535                                 "\n".join(["    %s=%s" % (k, v) for (k, v) in os.environ.items()]))
536                server = smtplib.SMTP(self.smtpserver)
537                server.sendmail(admin, [admin, crashrecipient], \
538                                       "From: %s\nTo: %s\nCc: %s\nSubject: PyKota v%s crash traceback !\n\n%s" % \
539                                       (admin, crashrecipient, admin, version.__version__, fullmessage))
540                server.quit()
541        except :
542            pass
543       
544    def parseCommandline(self, argv, short, long, allownothing=0) :
545        """Parses the command line, controlling options."""
546        # split options in two lists: those which need an argument, those which don't need any
547        withoutarg = []
548        witharg = []
549        lgs = len(short)
550        i = 0
551        while i < lgs :
552            ii = i + 1
553            if (ii < lgs) and (short[ii] == ':') :
554                # needs an argument
555                witharg.append(short[i])
556                ii = ii + 1 # skip the ':'
557            else :
558                # doesn't need an argument
559                withoutarg.append(short[i])
560            i = ii
561               
562        for option in long :
563            if option[-1] == '=' :
564                # needs an argument
565                witharg.append(option[:-1])
566            else :
567                # doesn't need an argument
568                withoutarg.append(option)
569       
570        # we begin with all possible options unset
571        parsed = {}
572        for option in withoutarg + witharg :
573            parsed[option] = None
574       
575        # then we parse the command line
576        args = []       # to not break if something unexpected happened
577        try :
578            options, args = getopt.getopt(argv, short, long)
579            if options :
580                for (o, v) in options :
581                    # we skip the '-' chars
582                    lgo = len(o)
583                    i = 0
584                    while (i < lgo) and (o[i] == '-') :
585                        i = i + 1
586                    o = o[i:]
587                    if o in witharg :
588                        # needs an argument : set it
589                        parsed[o] = v
590                    elif o in withoutarg :
591                        # doesn't need an argument : boolean
592                        parsed[o] = 1
593                    else :
594                        # should never occur
595                        raise PyKotaToolError, "Unexpected problem when parsing command line"
596            elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
597                self.display_usage_and_quit()
598        except getopt.error, msg :
599            self.printInfo(msg)
600            self.display_usage_and_quit()
601        return (parsed, args)
602   
603    def isValidName(self, name) :
604        """Checks if a user or printer name is valid."""
605        invalidchars = "/@"
606        for c in list(invalidchars) :
607            if c in name :
608                return 0
609        return 1       
610       
611    def matchString(self, s, patterns) :
612        """Returns 1 if the string s matches one of the patterns, else 0."""
613        for pattern in patterns :
614            if fnmatch.fnmatchcase(s, pattern) :
615                return 1
616        return 0
617       
618    def sendMessage(self, adminmail, touser, fullmessage) :
619        """Sends an email message containing headers to some user."""
620        if "@" not in touser :
621            touser = "%s@%s" % (touser, self.maildomain or self.smtpserver)
622        try :   
623            server = smtplib.SMTP(self.smtpserver)
624        except socket.error, msg :   
625            self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
626        else :
627            try :
628                server.sendmail(adminmail, [touser], "From: %s\nTo: %s\n%s" % (adminmail, touser, fullmessage))
629            except smtplib.SMTPException, answer :   
630                for (k, v) in answer.recipients.items() :
631                    self.printInfo(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
632            server.quit()
633       
634    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
635        """Sends an email message to a user."""
636        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
637        self.sendMessage(adminmail, user.Email or user.Name, "Subject: %s\n\n%s" % (subject, message))
638       
639    def sendMessageToAdmin(self, adminmail, subject, message) :
640        """Sends an email message to the Print Quota administrator."""
641        self.sendMessage(adminmail, adminmail, "Subject: %s\n\n%s" % (subject, message))
642       
643    def _checkUserPQuota(self, userpquota) :           
644        """Checks the user quota on a printer and deny or accept the job."""
645        # then we check the user's own quota
646        # if we get there we are sure that policy is not EXTERNAL
647        user = userpquota.User
648        printer = userpquota.Printer
649        enforcement = self.config.getPrinterEnforcement(printer.Name)
650        self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
651        (policy, dummy) = self.config.getPrinterPolicy(userpquota.Printer.Name)
652        if not userpquota.Exists :
653            # Unknown userquota
654            if policy == "ALLOW" :
655                action = "POLICY_ALLOW"
656            else :   
657                action = "POLICY_DENY"
658            self.printInfo(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
659        else :   
660            pagecounter = int(userpquota.PageCounter or 0)
661            if enforcement == "STRICT" :
662                pagecounter += self.softwareJobSize
663            if userpquota.SoftLimit is not None :
664                softlimit = int(userpquota.SoftLimit)
665                if pagecounter < softlimit :
666                    action = "ALLOW"
667                else :   
668                    if userpquota.HardLimit is None :
669                        # only a soft limit, this is equivalent to having only a hard limit
670                        action = "DENY"
671                    else :   
672                        hardlimit = int(userpquota.HardLimit)
673                        if softlimit <= pagecounter < hardlimit :   
674                            now = DateTime.now()
675                            if userpquota.DateLimit is not None :
676                                datelimit = DateTime.ISO.ParseDateTime(userpquota.DateLimit)
677                            else :
678                                datelimit = now + self.config.getGraceDelay(printer.Name)
679                                userpquota.setDateLimit(datelimit)
680                            if now < datelimit :
681                                action = "WARN"
682                            else :   
683                                action = "DENY"
684                        else :         
685                            action = "DENY"
686            else :       
687                if userpquota.HardLimit is not None :
688                    # no soft limit, only a hard one.
689                    hardlimit = int(userpquota.HardLimit)
690                    if pagecounter < hardlimit :
691                        action = "ALLOW"
692                    else :     
693                        action = "DENY"
694                else :
695                    # Both are unset, no quota, i.e. accounting only
696                    action = "ALLOW"
697        return action
698   
699    def checkGroupPQuota(self, grouppquota) :   
700        """Checks the group quota on a printer and deny or accept the job."""
701        group = grouppquota.Group
702        printer = grouppquota.Printer
703        enforcement = self.config.getPrinterEnforcement(printer.Name)
704        self.logdebug("Checking group %s's quota on printer %s" % (group.Name, printer.Name))
705        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
706            val = group.AccountBalance
707            if enforcement == "STRICT" : 
708                val -= self.softwareJobPrice # use precomputed size.
709            if val <= 0.0 :
710                action = "DENY"
711            elif val <= self.config.getPoorMan() :   
712                action = "WARN"
713            else :   
714                action = "ALLOW"
715            if (enforcement == "STRICT") and (val == 0.0) :
716                action = "WARN" # we can still print until account is 0
717        else :
718            val = grouppquota.PageCounter
719            if enforcement == "STRICT" :
720                val += self.softwareJobSize
721            if grouppquota.SoftLimit is not None :
722                softlimit = int(grouppquota.SoftLimit)
723                if val < softlimit :
724                    action = "ALLOW"
725                else :   
726                    if grouppquota.HardLimit is None :
727                        # only a soft limit, this is equivalent to having only a hard limit
728                        action = "DENY"
729                    else :   
730                        hardlimit = int(grouppquota.HardLimit)
731                        if softlimit <= val < hardlimit :   
732                            now = DateTime.now()
733                            if grouppquota.DateLimit is not None :
734                                datelimit = DateTime.ISO.ParseDateTime(grouppquota.DateLimit)
735                            else :
736                                datelimit = now + self.config.getGraceDelay(printer.Name)
737                                grouppquota.setDateLimit(datelimit)
738                            if now < datelimit :
739                                action = "WARN"
740                            else :   
741                                action = "DENY"
742                        else :         
743                            action = "DENY"
744            else :       
745                if grouppquota.HardLimit is not None :
746                    # no soft limit, only a hard one.
747                    hardlimit = int(grouppquota.HardLimit)
748                    if val < hardlimit :
749                        action = "ALLOW"
750                    else :     
751                        action = "DENY"
752                else :
753                    # Both are unset, no quota, i.e. accounting only
754                    action = "ALLOW"
755        return action
756   
757    def checkUserPQuota(self, userpquota) :
758        """Checks the user quota on a printer and all its parents and deny or accept the job."""
759        user = userpquota.User
760        printer = userpquota.Printer
761       
762        # indicates that a warning needs to be sent
763        warned = 0               
764       
765        # first we check any group the user is a member of
766        for group in self.storage.getUserGroups(user) :
767            grouppquota = self.storage.getGroupPQuota(group, printer)
768            # for the printer and all its parents
769            for gpquota in [ grouppquota ] + grouppquota.ParentPrintersGroupPQuota :
770                if gpquota.Exists :
771                    action = self.checkGroupPQuota(gpquota)
772                    if action == "DENY" :
773                        return action
774                    elif action == "WARN" :   
775                        warned = 1
776                       
777        # Then we check the user's account balance
778        # if we get there we are sure that policy is not EXTERNAL
779        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
780        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
781            self.logdebug("Checking account balance for user %s" % user.Name)
782            if user.AccountBalance is None :
783                if policy == "ALLOW" :
784                    action = "POLICY_ALLOW"
785                else :   
786                    action = "POLICY_DENY"
787                self.printInfo(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
788                return action       
789            else :   
790                val = float(user.AccountBalance or 0.0)
791                enforcement = self.config.getPrinterEnforcement(printer.Name)
792                if enforcement == "STRICT" : 
793                    val -= self.softwareJobPrice # use precomputed size.
794                if val <= 0.0 :
795                    action = "DENY"
796                elif val <= self.config.getPoorMan() :   
797                    action = "WARN"
798                else :
799                    action = "ALLOW"
800                if (enforcement == "STRICT") and (val == 0.0) :
801                    action = "WARN" # we can still print until account is 0
802                return action   
803        else :
804            # Then check the user quota on current printer and all its parents.               
805            policyallowed = 0
806            for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota :               
807                action = self._checkUserPQuota(upquota)
808                if action in ("DENY", "POLICY_DENY") :
809                    return action
810                elif action == "WARN" :   
811                    warned = 1
812                elif action == "POLICY_ALLOW" :   
813                    policyallowed = 1
814            if warned :       
815                return "WARN"
816            elif policyallowed :   
817                return "POLICY_ALLOW" 
818            else :   
819                return "ALLOW"
820               
821    def externalMailTo(self, cmd, action, user, printer, message) :
822        """Warns the user with an external command."""
823        username = user.Name
824        printername = printer.Name
825        email = user.Email or user.Name
826        if "@" not in email :
827            email = "%s@%s" % (email, self.maildomain or self.smtpserver)
828        os.system(cmd % locals())
829   
830    def formatCommandLine(self, cmd, user, printer) :
831        """Executes an external command."""
832        username = user.Name
833        printername = printer.Name
834        return cmd % locals()
835       
836    def warnGroupPQuota(self, grouppquota) :
837        """Checks a group quota and send messages if quota is exceeded on current printer."""
838        group = grouppquota.Group
839        printer = grouppquota.Printer
840        admin = self.config.getAdmin(printer.Name)
841        adminmail = self.config.getAdminMail(printer.Name)
842        (mailto, arguments) = self.config.getMailTo(printer.Name)
843        action = self.checkGroupPQuota(grouppquota)
844        if action.startswith("POLICY_") :
845            action = action[7:]
846        if action == "DENY" :
847            adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
848            self.printInfo(adminmessage)
849            if mailto in [ "BOTH", "ADMIN" ] :
850                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
851            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
852                for user in self.storage.getGroupMembers(group) :
853                    if mailto != "EXTERNAL" :
854                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
855                    else :   
856                        self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printer.Name))
857        elif action == "WARN" :   
858            adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
859            self.printInfo(adminmessage)
860            if mailto in [ "BOTH", "ADMIN" ] :
861                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
862            if group.LimitBy and (group.LimitBy.lower() == "balance") : 
863                message = self.config.getPoorWarn()
864            else :     
865                message = self.config.getSoftWarn(printer.Name)
866            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
867                for user in self.storage.getGroupMembers(group) :
868                    if mailto != "EXTERNAL" :
869                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
870                    else :   
871                        self.externalMailTo(arguments, action, user, printer, message)
872        return action       
873       
874    def warnUserPQuota(self, userpquota) :
875        """Checks a user quota and send him a message if quota is exceeded on current printer."""
876        user = userpquota.User
877        printer = userpquota.Printer
878        admin = self.config.getAdmin(printer.Name)
879        adminmail = self.config.getAdminMail(printer.Name)
880        (mailto, arguments) = self.config.getMailTo(printer.Name)
881        action = self.checkUserPQuota(userpquota)
882        if action.startswith("POLICY_") :
883            action = action[7:]
884        if action == "DENY" :
885            adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
886            self.printInfo(adminmessage)
887            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
888                message = self.config.getHardWarn(printer.Name)
889                if mailto != "EXTERNAL" :
890                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
891                else :   
892                    self.externalMailTo(arguments, action, user, printer, message)
893            if mailto in [ "BOTH", "ADMIN" ] :
894                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
895        elif action == "WARN" :   
896            adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
897            self.printInfo(adminmessage)
898            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
899                if user.LimitBy and (user.LimitBy.lower() == "balance") : 
900                    message = self.config.getPoorWarn()
901                else :     
902                    message = self.config.getSoftWarn(printer.Name)
903                if mailto != "EXTERNAL" :   
904                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
905                else :   
906                    self.externalMailTo(arguments, action, user, printer, message)
907            if mailto in [ "BOTH", "ADMIN" ] :
908                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
909        return action       
910       
911class PyKotaFilterOrBackend(PyKotaTool) :   
912    """Class for the PyKota filter or backend."""
913    def __init__(self) :
914        """Initialize local datas from current environment."""
915        # We begin with ignoring signals, we may de-ignore them later on.
916        self.gotSigTerm = 0
917        signal.signal(signal.SIGTERM, signal.SIG_IGN)
918        # signal.signal(signal.SIGCHLD, signal.SIG_IGN)
919        signal.signal(signal.SIGPIPE, signal.SIG_IGN)
920       
921        PyKotaTool.__init__(self)
922        (self.printingsystem, \
923         self.printerhostname, \
924         self.printername, \
925         self.username, \
926         self.jobid, \
927         self.inputfile, \
928         self.copies, \
929         self.title, \
930         self.options, \
931         self.originalbackend) = self.extractInfoFromCupsOrLprng()
932         
933        self.logdebug(_("Printing system %s, args=%s") % (str(self.printingsystem), " ".join(sys.argv)))
934       
935        self.username = self.username or 'root' # when printing test page from CUPS web interface, username is empty
936        if self.config.getUserNameToLower() :
937            self.username = self.username.lower()
938        self.preserveinputfile = self.inputfile 
939        try :
940            self.accounter = accounter.openAccounter(self)
941        except (config.PyKotaConfigError, accounter.PyKotaAccounterError), msg :   
942            self.crashed(msg)
943            raise
944        self.exportJobInfo()
945        self.jobdatastream = self.openJobDataStream()
946        os.environ["PYKOTAJOBSIZEBYTES"] = str(self.jobSizeBytes)
947        self.logdebug("Job size is %s bytes" % self.jobSizeBytes)
948        self.logdebug("Capturing SIGTERM events.")
949        signal.signal(signal.SIGTERM, self.sigterm_handler)
950       
951    def sendBackChannelData(self, message, level="info") :   
952        """Sends an informational message to CUPS via back channel stream (stderr)."""
953        sys.stderr.write("%s: PyKota (PID %s) : %s\n" % (level.upper(), os.getpid(), message.strip()))
954        sys.stderr.flush()
955       
956    def openJobDataStream(self) :   
957        """Opens the file which contains the job's datas."""
958        if self.preserveinputfile is None :
959            # Job comes from sys.stdin, but this is not
960            # seekable and complexifies our task, so create
961            # a temporary file and use it instead
962            self.logdebug("Duplicating data stream from stdin to temporary file")
963            dummy = 0
964            MEGABYTE = 1024*1024
965            self.jobSizeBytes = 0
966            infile = tempfile.TemporaryFile()
967            while 1 :
968                data = sys.stdin.read(MEGABYTE) 
969                if not data :
970                    break
971                self.jobSizeBytes += len(data)   
972                if not (dummy % 10) :
973                    self.logdebug("%s bytes read..." % self.jobSizeBytes)
974                dummy += 1   
975                infile.write(data)
976            self.logdebug("%s bytes read total." % self.jobSizeBytes)
977            infile.flush()   
978            infile.seek(0)
979            return infile
980        else :   
981            # real file, just open it
982            self.logdebug("Opening data stream %s" % self.preserveinputfile)
983            self.jobSizeBytes = os.stat(self.preserveinputfile)[6]
984            return open(self.preserveinputfile, "rb")
985       
986    def closeJobDataStream(self) :   
987        """Closes the file which contains the job's datas."""
988        self.logdebug("Closing data stream.")
989        try :
990            self.jobdatastream.close()
991        except :   
992            pass
993       
994    def precomputeJobSize(self) :   
995        """Computes the job size with a software method."""
996        self.logdebug("Precomputing job's size with generic PDL analyzer...")
997        self.jobdatastream.seek(0)
998        try :
999            parser = pdlanalyzer.PDLAnalyzer(self.jobdatastream)
1000            jobsize = parser.getJobSize()
1001        except pdlanalyzer.PDLAnalyzerError, msg :   
1002            # Here we just log the failure, but
1003            # we finally ignore it and return 0 since this
1004            # computation is just an indication of what the
1005            # job's size MAY be.
1006            self.printInfo(_("Unable to precompute the job's size with the generic PDL analyzer : %s") % msg, "warn")
1007            return 0
1008        else :   
1009            if ((self.printingsystem == "CUPS") \
1010                and (self.preserveinputfile is not None)) \
1011                or (self.printingsystem != "CUPS") :
1012                return jobsize * self.copies
1013            else :       
1014                return jobsize
1015           
1016    def sigterm_handler(self, signum, frame) :
1017        """Sets an attribute whenever SIGTERM is received."""
1018        self.gotSigTerm = 1
1019        os.environ["PYKOTASTATUS"] = "CANCELLED"
1020        self.printInfo(_("SIGTERM received, job %s cancelled.") % self.jobid)
1021       
1022    def exportJobInfo(self) :   
1023        """Exports job information to the environment."""
1024        os.environ["PYKOTAUSERNAME"] = str(self.username)
1025        os.environ["PYKOTAPRINTERNAME"] = str(self.printername)
1026        os.environ["PYKOTAJOBID"] = str(self.jobid)
1027        os.environ["PYKOTATITLE"] = self.title or ""
1028        os.environ["PYKOTAFILENAME"] = self.preserveinputfile or ""
1029        os.environ["PYKOTACOPIES"] = str(self.copies)
1030        os.environ["PYKOTAOPTIONS"] = self.options or ""
1031   
1032    def exportUserInfo(self, userpquota) :
1033        """Exports user information to the environment."""
1034        os.environ["PYKOTALIMITBY"] = str(userpquota.User.LimitBy)
1035        os.environ["PYKOTABALANCE"] = str(userpquota.User.AccountBalance or 0.0)
1036        os.environ["PYKOTALIFETIMEPAID"] = str(userpquota.User.LifeTimePaid or 0.0)
1037        os.environ["PYKOTAPAGECOUNTER"] = str(userpquota.PageCounter or 0)
1038        os.environ["PYKOTALIFEPAGECOUNTER"] = str(userpquota.LifePageCounter or 0)
1039        os.environ["PYKOTASOFTLIMIT"] = str(userpquota.SoftLimit)
1040        os.environ["PYKOTAHARDLIMIT"] = str(userpquota.HardLimit)
1041        os.environ["PYKOTADATELIMIT"] = str(userpquota.DateLimit)
1042       
1043        # not really an user information, but anyway
1044        # exports the list of printers groups the current
1045        # printer is a member of
1046        os.environ["PYKOTAPGROUPS"] = ",".join([p.Name for p in self.storage.getParentPrinters(userpquota.Printer)])
1047       
1048    def prehook(self, userpquota) :
1049        """Allows plugging of an external hook before the job gets printed."""
1050        prehook = self.config.getPreHook(userpquota.Printer.Name)
1051        if prehook :
1052            self.logdebug("Executing pre-hook [%s]" % prehook)
1053            os.system(prehook)
1054       
1055    def posthook(self, userpquota) :
1056        """Allows plugging of an external hook after the job gets printed and/or denied."""
1057        posthook = self.config.getPostHook(userpquota.Printer.Name)
1058        if posthook :
1059            self.logdebug("Executing post-hook [%s]" % posthook)
1060            os.system(posthook)
1061       
1062    def printInfo(self, message, level="info") :       
1063        """Sends a message to standard error."""
1064        self.logger.log_message("%s" % message, level)
1065       
1066    def extractInfoFromCupsOrLprng(self) :   
1067        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
1068       
1069           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
1070        """
1071        # Try to detect CUPS
1072        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
1073            if len(sys.argv) == 7 :
1074                inputfile = sys.argv[6]
1075            else :   
1076                inputfile = None
1077               
1078            # check that the DEVICE_URI environment variable's value is
1079            # prefixed with "cupspykota:" otherwise don't touch it.
1080            # If this is the case, we have to remove the prefix from
1081            # the environment before launching the real backend in cupspykota
1082            device_uri = os.environ.get("DEVICE_URI", "")
1083            if device_uri.startswith("cupspykota:") :
1084                fulldevice_uri = device_uri[:]
1085                device_uri = fulldevice_uri[len("cupspykota:"):]
1086                if device_uri.startswith("//") :    # lpd (at least)
1087                    device_uri = device_uri[2:]
1088                os.environ["DEVICE_URI"] = device_uri   # TODO : side effect !
1089            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
1090            try :
1091                (backend, destination) = device_uri.split(":", 1) 
1092            except ValueError :   
1093                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
1094            while destination.startswith("/") :
1095                destination = destination[1:]
1096            checkauth = destination.split("@", 1)   
1097            if len(checkauth) == 2 :
1098                destination = checkauth[1]
1099            printerhostname = destination.split("/")[0].split(":")[0]
1100            return ("CUPS", \
1101                    printerhostname, \
1102                    os.environ.get("PRINTER"), \
1103                    sys.argv[2].strip(), \
1104                    sys.argv[1].strip(), \
1105                    inputfile, \
1106                    int(sys.argv[4].strip()), \
1107                    sys.argv[3], \
1108                    sys.argv[5], \
1109                    backend)
1110        else :   
1111            # Try to detect LPRng
1112            # TODO : try to extract filename, options if available
1113            jseen = Jseen = Pseen = nseen = rseen = Kseen = None
1114            for arg in sys.argv :
1115                if arg.startswith("-j") :
1116                    jseen = arg[2:].strip()
1117                elif arg.startswith("-n") :     
1118                    nseen = arg[2:].strip()
1119                elif arg.startswith("-P") :   
1120                    Pseen = arg[2:].strip()
1121                elif arg.startswith("-r") :   
1122                    rseen = arg[2:].strip()
1123                elif arg.startswith("-J") :   
1124                    Jseen = arg[2:].strip()
1125                elif arg.startswith("-K") or arg.startswith("-#") :   
1126                    Kseen = int(arg[2:].strip())
1127            if Kseen is None :       
1128                Kseen = 1       # we assume the user wants at least one copy...
1129            if (rseen is None) and jseen and Pseen and nseen :   
1130                self.printInfo(_("Printer hostname undefined, set to 'localhost'"), "warn")
1131                rseen = "localhost"
1132            try :   
1133                df_name = [line[8:] for line in os.environ.get("HF", "").split() if line.startswith("df_name=")][0]
1134            except IndexError :
1135                try :
1136                    cftransfername = [line[15:] for line in os.environ.get("HF", "").split() if line.startswith("cftransfername=")][0]
1137                except IndexError :   
1138                    inputfile = None
1139                else :   
1140                    inputfile = os.path.join(os.environ.get("SPOOL_DIR", "."), "d" + cftransfername[1:])
1141            else :   
1142                inputfile = os.path.join(os.environ.get("SPOOL_DIR", "."), df_name)
1143            if jseen and Pseen and nseen and rseen :       
1144                options = os.environ.get("HF", "")
1145                return ("LPRNG", rseen, Pseen, nseen, jseen, inputfile, Kseen, Jseen, options, None)
1146        self.printInfo(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
1147        return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system
1148       
1149    def getPrinterUserAndUserPQuota(self) :       
1150        """Returns a tuple (policy, printer, user, and user print quota) on this printer.
1151       
1152           "OK" is returned in the policy if both printer, user and user print quota
1153           exist in the Quota Storage.
1154           Otherwise, the policy as defined for this printer in pykota.conf is returned.
1155           
1156           If policy was set to "EXTERNAL" and one of printer, user, or user print quota
1157           doesn't exist in the Quota Storage, then an external command is launched, as
1158           defined in the external policy for this printer in pykota.conf
1159           This external command can do anything, like automatically adding printers
1160           or users, for example, and finally extracting printer, user and user print
1161           quota from the Quota Storage is tried a second time.
1162           
1163           "EXTERNALERROR" is returned in case policy was "EXTERNAL" and an error status
1164           was returned by the external command.
1165        """
1166        for passnumber in range(1, 3) :
1167            printer = self.storage.getPrinter(self.printername)
1168            user = self.storage.getUser(self.username)
1169            userpquota = self.storage.getUserPQuota(user, printer)
1170            if printer.Exists and user.Exists and userpquota.Exists :
1171                policy = "OK"
1172                break
1173            (policy, args) = self.config.getPrinterPolicy(self.printername)
1174            if policy == "EXTERNAL" :   
1175                commandline = self.formatCommandLine(args, user, printer)
1176                if not printer.Exists :
1177                    self.printInfo(_("Printer %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (self.printername, commandline, self.printername))
1178                if not user.Exists :
1179                    self.printInfo(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (self.username, commandline, self.printername))
1180                if not userpquota.Exists :
1181                    self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying external policy (%s) for printer %s") % (self.username, self.printername, commandline, self.printername))
1182                if os.system(commandline) :
1183                    self.printInfo(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, self.printername), "error")
1184                    policy = "EXTERNALERROR"
1185                    break
1186            else :       
1187                if not printer.Exists :
1188                    self.printInfo(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.printername, policy))
1189                if not user.Exists :
1190                    self.printInfo(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (self.username, policy, self.printername))
1191                if not userpquota.Exists :
1192                    self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.username, self.printername, policy))
1193                break
1194        if policy == "EXTERNAL" :   
1195            if not printer.Exists :
1196                self.printInfo(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.printername)
1197            if not user.Exists :
1198                self.printInfo(_("User %s still not registered in the PyKota system, job will be rejected on printer %s") % (self.username, self.printername))
1199            if not userpquota.Exists :
1200                self.printInfo(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.username, self.printername))
1201        return (policy, printer, user, userpquota)
1202       
1203    def mainWork(self) :   
1204        """Main work is done here."""
1205        (policy, printer, user, userpquota) = self.getPrinterUserAndUserPQuota()
1206        # TODO : check for last user's quota in case pykota filter is used with querying
1207        if policy == "EXTERNALERROR" :
1208            # Policy was 'EXTERNAL' and the external command returned an error code
1209            return self.removeJob()
1210        elif policy == "EXTERNAL" :
1211            # Policy was 'EXTERNAL' and the external command wasn't able
1212            # to add either the printer, user or user print quota
1213            return self.removeJob()
1214        elif policy == "DENY" :   
1215            # Either printer, user or user print quota doesn't exist,
1216            # and the job should be rejected.
1217            return self.removeJob()
1218        else :
1219            if policy not in ("OK", "ALLOW") :
1220                self.printInfo(_("Invalid policy %s for printer %s") % (policy, self.printername))
1221                return self.removeJob()
1222            else :
1223                return self.doWork(policy, printer, user, userpquota)
Note: See TracBrowser for help on using the browser.