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

Revision 1227, 29.2 kB (checked in by jalet, 20 years ago)

Added 'utolower' configuration option to convert all usernames to
lowercase when printing. All database accesses are still and will
remain case sensitive though.

  • 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 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.63  2003/11/29 20:06:20  jalet
25# Added 'utolower' configuration option to convert all usernames to
26# lowercase when printing. All database accesses are still and will
27# remain case sensitive though.
28#
29# Revision 1.62  2003/11/25 22:37:22  jalet
30# Small code move
31#
32# Revision 1.61  2003/11/25 22:03:28  jalet
33# No more message on stderr when the translation is not available.
34#
35# Revision 1.60  2003/11/25 21:54:05  jalet
36# updated FAQ
37#
38# Revision 1.59  2003/11/25 13:33:43  jalet
39# Puts 'root' instead of '' when printing from CUPS web interface (which
40# gives an empty username)
41#
42# Revision 1.58  2003/11/21 14:28:45  jalet
43# More complete job history.
44#
45# Revision 1.57  2003/11/19 23:19:38  jalet
46# Code refactoring work.
47# Explicit redirection to /dev/null has to be set in external policy now, just
48# like in external mailto.
49#
50# Revision 1.56  2003/11/19 07:40:20  jalet
51# Missing import statement.
52# Better documentation for mailto: external(...)
53#
54# Revision 1.55  2003/11/18 23:43:12  jalet
55# Mailto can be any external command now, as usual.
56#
57# Revision 1.54  2003/10/24 21:52:46  jalet
58# Now can force language when coming from CGI script.
59#
60# Revision 1.53  2003/10/08 21:41:38  jalet
61# External policies for printers works !
62# We can now auto-add users on first print, and do other useful things if needed.
63#
64# Revision 1.52  2003/10/07 09:07:28  jalet
65# Character encoding added to please latest version of Python
66#
67# Revision 1.51  2003/10/06 14:21:41  jalet
68# Test reversed to not retrieve group members when no messages for them.
69#
70# Revision 1.50  2003/10/02 20:23:18  jalet
71# Storage caching mechanism added.
72#
73# Revision 1.49  2003/07/29 20:55:17  jalet
74# 1.14 is out !
75#
76# Revision 1.48  2003/07/21 23:01:56  jalet
77# Modified some messages aout soft limit
78#
79# Revision 1.47  2003/07/16 21:53:08  jalet
80# Really big modifications wrt new configuration file's location and content.
81#
82# Revision 1.46  2003/07/09 20:17:07  jalet
83# Email field added to PostgreSQL schema
84#
85# Revision 1.45  2003/07/08 19:43:51  jalet
86# Configurable warning messages.
87# Poor man's treshold value added.
88#
89# Revision 1.44  2003/07/07 11:49:24  jalet
90# Lots of small fixes with the help of PyChecker
91#
92# Revision 1.43  2003/07/04 09:06:32  jalet
93# Small bug fix wrt undefined "LimitBy" field.
94#
95# Revision 1.42  2003/06/30 12:46:15  jalet
96# Extracted reporting code.
97#
98# Revision 1.41  2003/06/25 14:10:01  jalet
99# Hey, it may work (edpykota --reset excepted) !
100#
101# Revision 1.40  2003/06/10 16:37:54  jalet
102# Deletion of the second user which is not needed anymore.
103# Added a debug configuration field in /etc/pykota.conf
104# All queries can now be sent to the logger in debug mode, this will
105# greatly help improve performance when time for this will come.
106#
107# Revision 1.39  2003/04/29 18:37:54  jalet
108# Pluggable accounting methods (actually doesn't support external scripts)
109#
110# Revision 1.38  2003/04/24 11:53:48  jalet
111# Default policy for unknown users/groups is to DENY printing instead
112# of the previous default to ALLOW printing. This is to solve an accuracy
113# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
114# (from PyKota's POV) will be charged to the next user who prints on the
115# same printer.
116#
117# Revision 1.37  2003/04/24 08:08:27  jalet
118# Debug message forgotten
119#
120# Revision 1.36  2003/04/24 07:59:40  jalet
121# LPRng support now works !
122#
123# Revision 1.35  2003/04/23 22:13:57  jalet
124# Preliminary support for LPRng added BUT STILL UNTESTED.
125#
126# Revision 1.34  2003/04/17 09:26:21  jalet
127# repykota now reports account balances too.
128#
129# Revision 1.33  2003/04/16 12:35:49  jalet
130# Groups quota work now !
131#
132# Revision 1.32  2003/04/16 08:53:14  jalet
133# Printing can now be limited either by user's account balance or by
134# page quota (the default). Quota report doesn't include account balance
135# yet, though.
136#
137# Revision 1.31  2003/04/15 11:30:57  jalet
138# More work done on money print charging.
139# Minor bugs corrected.
140# All tools now access to the storage as priviledged users, repykota excepted.
141#
142# Revision 1.30  2003/04/10 21:47:20  jalet
143# Job history added. Upgrade script neutralized for now !
144#
145# Revision 1.29  2003/03/29 13:45:27  jalet
146# GPL paragraphs were incorrectly (from memory) copied into the sources.
147# Two README files were added.
148# Upgrade script for PostgreSQL pre 1.01 schema was added.
149#
150# Revision 1.28  2003/03/29 13:08:28  jalet
151# Configuration is now expected to be found in /etc/pykota.conf instead of
152# in /etc/cups/pykota.conf
153# Installation script can move old config files to the new location if needed.
154# Better error handling if configuration file is absent.
155#
156# Revision 1.27  2003/03/15 23:01:28  jalet
157# New mailto option in configuration file added.
158# No time to test this tonight (although it should work).
159#
160# Revision 1.26  2003/03/09 23:58:16  jalet
161# Comment
162#
163# Revision 1.25  2003/03/07 22:56:14  jalet
164# 0.99 is out with some bug fixes.
165#
166# Revision 1.24  2003/02/27 23:48:41  jalet
167# Correctly maps PyKota's log levels to syslog log levels
168#
169# Revision 1.23  2003/02/27 22:55:20  jalet
170# WARN log priority doesn't exist.
171#
172# Revision 1.22  2003/02/27 09:09:20  jalet
173# Added a method to match strings against wildcard patterns
174#
175# Revision 1.21  2003/02/17 23:01:56  jalet
176# Typos
177#
178# Revision 1.20  2003/02/17 22:55:01  jalet
179# More options can now be set per printer or globally :
180#
181#       admin
182#       adminmail
183#       gracedelay
184#       requester
185#
186# the printer option has priority when both are defined.
187#
188# Revision 1.19  2003/02/10 11:28:45  jalet
189# Localization
190#
191# Revision 1.18  2003/02/10 01:02:17  jalet
192# External requester is about to work, but I must sleep
193#
194# Revision 1.17  2003/02/09 13:05:43  jalet
195# Internationalization continues...
196#
197# Revision 1.16  2003/02/09 12:56:53  jalet
198# Internationalization begins...
199#
200# Revision 1.15  2003/02/08 22:09:52  jalet
201# Name check method moved here
202#
203# Revision 1.14  2003/02/07 10:42:45  jalet
204# Indentation problem
205#
206# Revision 1.13  2003/02/07 08:34:16  jalet
207# Test wrt date limit was wrong
208#
209# Revision 1.12  2003/02/06 23:20:02  jalet
210# warnpykota doesn't need any user/group name argument, mimicing the
211# warnquota disk quota tool.
212#
213# Revision 1.11  2003/02/06 22:54:33  jalet
214# warnpykota should be ok
215#
216# Revision 1.10  2003/02/06 15:03:11  jalet
217# added a method to set the limit date
218#
219# Revision 1.9  2003/02/06 10:39:23  jalet
220# Preliminary edpykota work.
221#
222# Revision 1.8  2003/02/06 09:19:02  jalet
223# More robust behavior (hopefully) when the user or printer is not managed
224# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
225# printer and/or user not 'yet?' in storage.
226#
227# Revision 1.7  2003/02/06 00:00:45  jalet
228# Now includes the printer name in email messages
229#
230# Revision 1.6  2003/02/05 23:55:02  jalet
231# Cleaner email messages
232#
233# Revision 1.5  2003/02/05 23:45:09  jalet
234# Better DateTime manipulation wrt grace delay
235#
236# Revision 1.4  2003/02/05 23:26:22  jalet
237# Incorrect handling of grace delay
238#
239# Revision 1.3  2003/02/05 22:16:20  jalet
240# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
241#
242# Revision 1.2  2003/02/05 22:10:29  jalet
243# Typos
244#
245# Revision 1.1  2003/02/05 21:28:17  jalet
246# Initial import into CVS
247#
248#
249#
250
251import sys
252import os
253import fnmatch
254import getopt
255import smtplib
256import gettext
257import locale
258
259from mx import DateTime
260
261from pykota import version, config, storage, logger, accounter
262
263class PyKotaToolError(Exception):
264    """An exception for PyKota config related stuff."""
265    def __init__(self, message = ""):
266        self.message = message
267        Exception.__init__(self, message)
268    def __repr__(self):
269        return self.message
270    __str__ = __repr__
271   
272class PyKotaTool :   
273    """Base class for all PyKota command line tools."""
274    def __init__(self, lang=None, doc="PyKota %s (c) 2003 %s" % (version.__version__, version.__author__)) :
275        """Initializes the command line tool."""
276        # locale stuff
277        try :
278            locale.setlocale(locale.LC_ALL, lang)
279            gettext.install("pykota")
280        except (locale.Error, IOError) :
281            gettext.NullTranslations().install()
282            # sys.stderr.write("PyKota : Error while loading translations\n")
283   
284        # pykota specific stuff
285        self.documentation = doc
286        self.config = config.PyKotaConfig("/etc/pykota")
287        self.logger = logger.openLogger(self, self.config.getLoggingBackend())
288        self.debug = self.config.getDebug()
289        self.storage = storage.openConnection(self)
290        self.smtpserver = self.config.getSMTPServer()
291       
292    def logdebug(self, message) :   
293        """Logs something to debug output if debug is enabled."""
294        if self.debug :
295            self.logger.log_message(message, "debug")
296       
297    def clean(self) :   
298        """Ensures that the database is closed."""
299        try :
300            self.storage.close()
301        except (TypeError, NameError, AttributeError) :   
302            pass
303           
304    def display_version_and_quit(self) :
305        """Displays version number, then exists successfully."""
306        self.clean()
307        print version.__version__
308        sys.exit(0)
309   
310    def display_usage_and_quit(self) :
311        """Displays command line usage, then exists successfully."""
312        self.clean()
313        print self.documentation
314        sys.exit(0)
315       
316    def parseCommandline(self, argv, short, long, allownothing=0) :
317        """Parses the command line, controlling options."""
318        # split options in two lists: those which need an argument, those which don't need any
319        withoutarg = []
320        witharg = []
321        lgs = len(short)
322        i = 0
323        while i < lgs :
324            ii = i + 1
325            if (ii < lgs) and (short[ii] == ':') :
326                # needs an argument
327                witharg.append(short[i])
328                ii = ii + 1 # skip the ':'
329            else :
330                # doesn't need an argument
331                withoutarg.append(short[i])
332            i = ii
333               
334        for option in long :
335            if option[-1] == '=' :
336                # needs an argument
337                witharg.append(option[:-1])
338            else :
339                # doesn't need an argument
340                withoutarg.append(option)
341       
342        # we begin with all possible options unset
343        parsed = {}
344        for option in withoutarg + witharg :
345            parsed[option] = None
346       
347        # then we parse the command line
348        args = []       # to not break if something unexpected happened
349        try :
350            options, args = getopt.getopt(argv, short, long)
351            if options :
352                for (o, v) in options :
353                    # we skip the '-' chars
354                    lgo = len(o)
355                    i = 0
356                    while (i < lgo) and (o[i] == '-') :
357                        i = i + 1
358                    o = o[i:]
359                    if o in witharg :
360                        # needs an argument : set it
361                        parsed[o] = v
362                    elif o in withoutarg :
363                        # doesn't need an argument : boolean
364                        parsed[o] = 1
365                    else :
366                        # should never occur
367                        raise PyKotaToolError, "Unexpected problem when parsing command line"
368            elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
369                self.display_usage_and_quit()
370        except getopt.error, msg :
371            sys.stderr.write("%s\n" % msg)
372            sys.stderr.flush()
373            self.display_usage_and_quit()
374        return (parsed, args)
375   
376    def isValidName(self, name) :
377        """Checks if a user or printer name is valid."""
378        # unfortunately Python 2.1 string modules doesn't define ascii_letters...
379        asciiletters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
380        digits = '0123456789'
381        if name[0] in asciiletters :
382            validchars = asciiletters + digits + "-_"
383            for c in name[1:] :
384                if c not in validchars :
385                    return 0
386            return 1       
387        return 0
388       
389    def matchString(self, s, patterns) :
390        """Returns 1 if the string s matches one of the patterns, else 0."""
391        for pattern in patterns :
392            if fnmatch.fnmatchcase(s, pattern) :
393                return 1
394        return 0
395       
396    def sendMessage(self, adminmail, touser, fullmessage) :
397        """Sends an email message containing headers to some user."""
398        if "@" not in touser :
399            touser = "%s@%s" % (touser, self.smtpserver)
400        server = smtplib.SMTP(self.smtpserver)
401        try :
402            server.sendmail(adminmail, [touser], fullmessage)
403        except smtplib.SMTPRecipientsRefused, answer :   
404            for (k, v) in answer.recipients.items() :
405                self.logger.log_message(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
406        server.quit()
407       
408    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
409        """Sends an email message to a user."""
410        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
411        self.sendMessage(adminmail, user.Email or user.Name, "Subject: %s\n\n%s" % (subject, message))
412       
413    def sendMessageToAdmin(self, adminmail, subject, message) :
414        """Sends an email message to the Print Quota administrator."""
415        self.sendMessage(adminmail, adminmail, "Subject: %s\n\n%s" % (subject, message))
416       
417    def checkGroupPQuota(self, grouppquota) :   
418        """Checks the group quota on a printer and deny or accept the job."""
419        group = grouppquota.Group
420        printer = grouppquota.Printer
421        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
422            if group.AccountBalance <= 0.0 :
423                action = "DENY"
424            elif group.AccountBalance <= self.config.getPoorMan() :   
425                action = "WARN"
426            else :   
427                action = "ALLOW"
428        else :
429            if grouppquota.SoftLimit is not None :
430                softlimit = int(grouppquota.SoftLimit)
431                if grouppquota.PageCounter < softlimit :
432                    action = "ALLOW"
433                else :   
434                    if grouppquota.HardLimit is None :
435                        # only a soft limit, this is equivalent to having only a hard limit
436                        action = "DENY"
437                    else :   
438                        hardlimit = int(grouppquota.HardLimit)
439                        if softlimit <= grouppquota.PageCounter < hardlimit :   
440                            now = DateTime.now()
441                            if grouppquota.DateLimit is not None :
442                                datelimit = DateTime.ISO.ParseDateTime(grouppquota.DateLimit)
443                            else :
444                                datelimit = now + self.config.getGraceDelay(printer.Name)
445                                grouppquota.setDateLimit(datelimit)
446                            if now < datelimit :
447                                action = "WARN"
448                            else :   
449                                action = "DENY"
450                        else :         
451                            action = "DENY"
452            else :       
453                if grouppquota.HardLimit is not None :
454                    # no soft limit, only a hard one.
455                    hardlimit = int(grouppquota.HardLimit)
456                    if grouppquota.PageCounter < hardlimit :
457                        action = "ALLOW"
458                    else :     
459                        action = "DENY"
460                else :
461                    # Both are unset, no quota, i.e. accounting only
462                    action = "ALLOW"
463        return action
464   
465    def checkUserPQuota(self, userpquota) :
466        """Checks the user quota on a printer and deny or accept the job."""
467        user = userpquota.User
468        printer = userpquota.Printer
469       
470        # first we check any group the user is a member of
471        for group in self.storage.getUserGroups(user) :
472            grouppquota = self.storage.getGroupPQuota(group, printer)
473            if grouppquota.Exists :
474                action = self.checkGroupPQuota(grouppquota)
475                if action == "DENY" :
476                    return action
477               
478        # then we check the user's own quota
479        # if we get there we are sure that policy is not EXTERNAL
480        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
481        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
482            if user.AccountBalance is None :
483                if policy == "ALLOW" :
484                    action = "POLICY_ALLOW"
485                else :   
486                    action = "POLICY_DENY"
487                self.logger.log_message(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
488            else :   
489                val = float(user.AccountBalance or 0.0)
490                if val <= 0.0 :
491                    action = "DENY"
492                elif val <= self.config.getPoorMan() :   
493                    action = "WARN"
494                else :   
495                    action = "ALLOW"
496        else :
497            if not userpquota.Exists :
498                # Unknown userquota
499                if policy == "ALLOW" :
500                    action = "POLICY_ALLOW"
501                else :   
502                    action = "POLICY_DENY"
503                self.logger.log_message(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
504            else :   
505                pagecounter = int(userpquota.PageCounter or 0)
506                if userpquota.SoftLimit is not None :
507                    softlimit = int(userpquota.SoftLimit)
508                    if pagecounter < softlimit :
509                        action = "ALLOW"
510                    else :   
511                        if userpquota.HardLimit is None :
512                            # only a soft limit, this is equivalent to having only a hard limit
513                            action = "DENY"
514                        else :   
515                            hardlimit = int(userpquota.HardLimit)
516                            if softlimit <= pagecounter < hardlimit :   
517                                now = DateTime.now()
518                                if userpquota.DateLimit is not None :
519                                    datelimit = DateTime.ISO.ParseDateTime(userpquota.DateLimit)
520                                else :
521                                    datelimit = now + self.config.getGraceDelay(printer.Name)
522                                    userpquota.setDateLimit(datelimit)
523                                if now < datelimit :
524                                    action = "WARN"
525                                else :   
526                                    action = "DENY"
527                            else :         
528                                action = "DENY"
529                else :       
530                    if userpquota.HardLimit is not None :
531                        # no soft limit, only a hard one.
532                        hardlimit = int(userpquota.HardLimit)
533                        if pagecounter < hardlimit :
534                            action = "ALLOW"
535                        else :     
536                            action = "DENY"
537                    else :
538                        # Both are unset, no quota, i.e. accounting only
539                        action = "ALLOW"
540        return action
541   
542    def externalMailTo(self, cmd, action, user, printer, message) :
543        """Warns the user with an external command."""
544        username = user.Name
545        printername = printer.Name
546        email = user.Email or user.Name
547        if "@" not in email :
548            email = "%s@%s" % (email, self.smtpserver)
549        os.system(cmd % locals())
550   
551    def formatCommandLine(self, cmd, user, printer) :
552        """Executes an external command."""
553        username = user.Name
554        printername = printer.Name
555        return cmd % locals()
556       
557    def warnGroupPQuota(self, grouppquota) :
558        """Checks a group quota and send messages if quota is exceeded on current printer."""
559        group = grouppquota.Group
560        printer = grouppquota.Printer
561        admin = self.config.getAdmin(printer.Name)
562        adminmail = self.config.getAdminMail(printer.Name)
563        (mailto, arguments) = self.config.getMailTo(printer.Name)
564        action = self.checkGroupPQuota(grouppquota)
565        if action.startswith("POLICY_") :
566            action = action[7:]
567        if action == "DENY" :
568            adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
569            self.logger.log_message(adminmessage)
570            if mailto in [ "BOTH", "ADMIN" ] :
571                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
572            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
573                for user in self.storage.getGroupMembers(group) :
574                    if mailto != "EXTERNAL" :
575                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
576                    else :   
577                        self.externalMailTo(arguments, action, user, printer, message)
578        elif action == "WARN" :   
579            adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
580            self.logger.log_message(adminmessage)
581            if mailto in [ "BOTH", "ADMIN" ] :
582                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
583            if group.LimitBy and (group.LimitBy.lower() == "balance") : 
584                message = self.config.getPoorWarn()
585            else :     
586                message = self.config.getSoftWarn(printer.Name)
587            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
588                for user in self.storage.getGroupMembers(group) :
589                    if mailto != "EXTERNAL" :
590                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
591                    else :   
592                        self.externalMailTo(arguments, action, user, printer, message)
593        return action       
594       
595    def warnUserPQuota(self, userpquota) :
596        """Checks a user quota and send him a message if quota is exceeded on current printer."""
597        user = userpquota.User
598        printer = userpquota.Printer
599        admin = self.config.getAdmin(printer.Name)
600        adminmail = self.config.getAdminMail(printer.Name)
601        (mailto, arguments) = self.config.getMailTo(printer.Name)
602        action = self.checkUserPQuota(userpquota)
603        if action.startswith("POLICY_") :
604            action = action[7:]
605        if action == "DENY" :
606            adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
607            self.logger.log_message(adminmessage)
608            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
609                message = self.config.getHardWarn(printer.Name)
610                if mailto != "EXTERNAL" :
611                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
612                else :   
613                    self.externalMailTo(arguments, action, user, printer, message)
614            if mailto in [ "BOTH", "ADMIN" ] :
615                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
616        elif action == "WARN" :   
617            adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
618            self.logger.log_message(adminmessage)
619            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
620                if user.LimitBy and (user.LimitBy.lower() == "balance") : 
621                    message = self.config.getPoorWarn()
622                else :     
623                    message = self.config.getSoftWarn(printer.Name)
624                if mailto != "EXTERNAL" :   
625                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
626                else :   
627                    self.externalMailTo(arguments, action, user, printer, message)
628            if mailto in [ "BOTH", "ADMIN" ] :
629                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
630        return action       
631       
632class PyKotaFilterOrBackend(PyKotaTool) :   
633    """Class for the PyKota filter or backend."""
634    def __init__(self) :
635        PyKotaTool.__init__(self)
636        (self.printingsystem, \
637         self.printerhostname, \
638         self.printername, \
639         self.username, \
640         self.jobid, \
641         self.inputfile, \
642         self.copies, \
643         self.title, \
644         self.options, \
645         self.originalbackend) = self.extractInfoFromCupsOrLprng()
646        self.username = self.username or 'root' 
647        if self.config.getUserNameToLower() :
648            self.username = self.username.lower()
649        self.preserveinputfile = self.inputfile 
650        self.accounter = accounter.openAccounter(self)
651   
652    def extractInfoFromCupsOrLprng(self) :   
653        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
654       
655           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
656        """
657        # Try to detect CUPS
658        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
659            if len(sys.argv) == 7 :
660                inputfile = sys.argv[6]
661            else :   
662                inputfile = None
663               
664            # check that the DEVICE_URI environment variable's value is
665            # prefixed with "cupspykota:" otherwise don't touch it.
666            # If this is the case, we have to remove the prefix from
667            # the environment before launching the real backend in cupspykota
668            device_uri = os.environ.get("DEVICE_URI", "")
669            if device_uri.startswith("cupspykota:") :
670                fulldevice_uri = device_uri[:]
671                device_uri = fulldevice_uri[len("cupspykota:"):]
672                if device_uri.startswith("//") :    # lpd (at least)
673                    device_uri = device_uri[2:]
674                os.environ["DEVICE_URI"] = device_uri   # TODO : side effect !
675            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
676            try :
677                (backend, destination) = device_uri.split(":", 1) 
678            except ValueError :   
679                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
680            while destination.startswith("/") :
681                destination = destination[1:]
682            printerhostname = destination.split("/")[0].split(":")[0]
683            return ("CUPS", \
684                    printerhostname, \
685                    os.environ.get("PRINTER"), \
686                    sys.argv[2].strip(), \
687                    sys.argv[1].strip(), \
688                    inputfile, \
689                    int(sys.argv[4].strip()), \
690                    sys.argv[3], \
691                    sys.argv[5], \
692                    backend)
693        else :   
694            # Try to detect LPRng
695            # TODO : try to extract filename, job's title, and options if available
696            jseen = Pseen = nseen = rseen = Kseen = None
697            for arg in sys.argv :
698                if arg.startswith("-j") :
699                    jseen = arg[2:].strip()
700                elif arg.startswith("-n") :     
701                    nseen = arg[2:].strip()
702                elif arg.startswith("-P") :   
703                    Pseen = arg[2:].strip()
704                elif arg.startswith("-r") :   
705                    rseen = arg[2:].strip()
706                elif arg.startswith("-K") or arg.startswith("-#") :   
707                    Kseen = int(arg[2:].strip())
708            if Kseen is None :       
709                Kseen = 1       # we assume the user wants at least one copy...
710            if (rseen is None) and jseen and Pseen and nseen :   
711                self.logger.log_message(_("Printer hostname undefined, set to 'localhost'"), "warn")
712                rseen = "localhost"
713            if jseen and Pseen and nseen and rseen :       
714                # job is always in stdin (None)
715                return ("LPRNG", rseen, Pseen, nseen, jseen, None, Kseen, None, None, None)
716        self.logger.log_message(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
717        return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system
Note: See TracBrowser for help on using the browser.