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

Revision 3294, 31.6 kB (checked in by jerome, 16 years ago)

Added modules to store utility functions and application
intialization code, which has nothing to do in classes.
Modified tool.py accordingly (far from being finished)
Use these new modules where necessary.
Now converts all command line arguments to unicode before
beginning to work. Added a proper logging method for already
encoded query strings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# -*- coding: UTF-8 -*-
2#
3# PyKota : Print Quotas for CUPS
4#
5# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19# $Id$
20#
21#
22
23"""This module defines the base classes for PyKota command line tools."""
24
25import sys
26import os
27import pwd
28import fnmatch
29import getopt
30import smtplib
31import locale
32import socket
33import time
34from email.MIMEText import MIMEText
35from email.Header import Header
36import email.Utils
37
38from mx import DateTime
39
40from pykota.errors import PyKotaCommandLineError
41from pykota import config, storage, logger
42from pykota.version import __version__, __author__, __years__, __gplblurb__
43
44def N_(message) :
45    """Fake translation marker for translatable strings extraction."""
46    return message
47
48def crashed(message="Bug in PyKota") :   
49    """Minimal crash method."""
50    import traceback
51    lines = []
52    for line in traceback.format_exception(*sys.exc_info()) :
53        lines.extend([l for l in line.split("\n") if l])
54    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKota v%s" % __version__, message] + lines)])
55    sys.stderr.write(msg)
56    sys.stderr.flush()
57    return msg
58
59class Percent :
60    """A class to display progress."""
61    def __init__(self, app, size=None) :
62        """Initializes the engine."""
63        self.app = app
64        self.size = None
65        if size :
66            self.setSize(size)
67        self.previous = None
68        self.before = time.time()
69       
70    def setSize(self, size) :     
71        """Sets the total size."""
72        self.number = 0
73        self.size = size
74        if size :
75            self.factor = 100.0 / float(size)
76       
77    def display(self, msg) :   
78        """Displays the value."""
79        self.app.display(msg)
80       
81    def oneMore(self) :   
82        """Increments internal counter."""
83        if self.size :
84            self.number += 1
85            percent = "%.02f" % (float(self.number) * self.factor)
86            if percent != self.previous : # optimize for large number of items
87                self.display("\r%s%%" % percent)
88                self.previous = percent
89           
90    def done(self) :         
91        """Displays the 'done' message."""
92        after = time.time()
93        if self.size :
94            try :
95                speed = self.size / ((after - self.before) + 0.00000000001) # adds an epsilon to avoid an user's problem I can't reproduce...
96            except ZeroDivisionError :   
97                speed = 1 # Fake value in case of division by zero, shouldn't happen anyway with the epsilon above...
98            self.display("\r100.00%%\r        \r%s. %s : %.2f %s.\n" \
99                     % (_("Done"), _("Average speed"), speed, _("entries per second")))
100        else :             
101            self.display("\r100.00%%\r        \r%s.\n" % _("Done"))
102       
103class Tool :
104    """Base class for tools with no database access."""
105    def __init__(self, lang="", charset=None, doc="PyKota v%(__version__)s (c) %(__years__)s %(__author__)s") :
106        """Initializes the command line tool."""
107        self.debug = True # in case of early failure
108        self.logger = logger.openLogger("stderr")
109       
110        # Saves a copy of the locale settings
111        (self.language, self.charset) = locale.getlocale()
112       
113        # pykota specific stuff
114        self.documentation = doc
115       
116        # Extract the effective username
117        uid = os.geteuid()
118        try :
119            self.effectiveUserName = pwd.getpwuid(uid)[0]
120        except (KeyError, IndexError), msg :   
121            self.printInfo(_("Strange problem with uid(%s) : %s") % (uid, msg), "warn")
122            self.effectiveUserName = os.getlogin()
123       
124    def deferredInit(self) :       
125        """Deferred initialization."""
126        confdir = os.environ.get("PYKOTA_HOME")
127        environHome = True
128        missingUser = False
129        if confdir is None :
130            environHome = False
131            # check for config files in the 'pykota' user's home directory.
132            try :
133                self.pykotauser = pwd.getpwnam("pykota")
134                confdir = self.pykotauser[5]
135            except KeyError :   
136                self.pykotauser = None
137                confdir = "/etc/pykota"
138                missingUser = True
139           
140        self.config = config.PyKotaConfig(confdir)
141        self.debug = self.config.getDebug()
142        self.smtpserver = self.config.getSMTPServer()
143        self.maildomain = self.config.getMailDomain()
144        self.logger = logger.openLogger(self.config.getLoggingBackend())
145           
146        # We NEED this here, even when not in an accounting filter/backend   
147        self.softwareJobSize = 0
148        self.softwareJobPrice = 0.0
149       
150        if environHome :
151            self.printInfo("PYKOTA_HOME environment variable is set. Configuration files were searched in %s" % confdir, "info")
152        else :
153            if missingUser :     
154                self.printInfo("The 'pykota' system account is missing. Configuration files were searched in %s instead." % confdir, "warn")
155       
156        self.logdebug("Language in use : %s" % self.language)
157        self.logdebug("Charset in use : %s" % self.charset)
158       
159        arguments = " ".join(['"%s"' % arg for arg in sys.argv])
160        self.logdebug("Command line arguments : %s" % arguments)
161       
162    def UTF8ToUserCharset(self, text) :
163        """Converts from UTF-8 to user's charset."""
164        if text is None :
165            return None
166        else :   
167            return text.decode("UTF-8", "replace").encode(self.charset, "replace") 
168       
169    def userCharsetToUTF8(self, text) :
170        """Converts from user's charset to UTF-8."""
171        if text is None :
172            return None
173        else :   
174            return text.decode(self.charset, "replace").encode("UTF-8", "replace")   
175       
176    def display(self, message) :
177        """Display a message but only if stdout is a tty."""
178        if sys.stdout.isatty() :
179            sys.stdout.write(message.encode(sys.stdout.encoding or "UTF-8", \
180                                            "replace"))
181            sys.stdout.flush()
182           
183    def logdebug(self, message) :   
184        """Logs something to debug output if debug is enabled."""
185        if self.debug :
186            self.logger.log_message(message.encode(sys.stdout.encoding \
187                                                       or "UTF-8", \
188                                                   "replace"), \
189                                    "debug")
190           
191    def printInfo(self, message, level="info") :       
192        """Sends a message to standard error."""
193        sys.stderr.write("%s: %s\n" % (level.upper(), \
194                                       message.encode(sys.stdout.encoding \
195                                                          or "UTF-8", \
196                                                      "replace")))
197        sys.stderr.flush()
198       
199    def matchString(self, s, patterns) :
200        """Returns True if the string s matches one of the patterns, else False."""
201        if not patterns :
202            return True # No pattern, always matches.
203        else :   
204            for pattern in patterns :
205                if fnmatch.fnmatchcase(s, pattern) :
206                    return True
207            return False
208       
209    def sanitizeNames(self, options, names) :
210        """Ensures that an user can only see the datas he is allowed to see, by modifying the list of names."""
211        if not self.config.isAdmin :
212            username = pwd.getpwuid(os.geteuid())[0]
213            if not options["list"] :
214                raise PyKotaCommandLineError, "%s : %s" % (username, _("You're not allowed to use this command."))
215            else :
216                if options["groups"] :
217                    user = self.storage.getUser(username)
218                    if user.Exists :
219                        return [ g.Name for g in self.storage.getUserGroups(user) ]
220                return [ username ]
221        elif not names :       
222            return ["*"]
223        else :   
224            return names
225       
226    def display_version_and_quit(self) :
227        """Displays version number, then exists successfully."""
228        try :
229            self.clean()
230        except AttributeError :   
231            pass
232        print __version__
233        sys.exit(0)
234   
235    def display_usage_and_quit(self) :
236        """Displays command line usage, then exists successfully."""
237        try :
238            self.clean()
239        except AttributeError :   
240            pass
241        print _(self.documentation) % globals()
242        print __gplblurb__
243        print
244        print _("Please report bugs to :"), __author__
245        sys.exit(0)
246       
247    def crashed(self, message="Bug in PyKota") :   
248        """Outputs a crash message, and optionally sends it to software author."""
249        msg = crashed(message)
250        fullmessage = "========== Traceback :\n\n%s\n\n========== sys.argv :\n\n%s\n\n========== Environment :\n\n%s\n" % \
251                        (msg, \
252                         "\n".join(["    %s" % repr(a) for a in sys.argv]), \
253                         "\n".join(["    %s=%s" % (k, v) for (k, v) in os.environ.items()]))
254        try :
255            crashrecipient = self.config.getCrashRecipient()
256            if crashrecipient :
257                admin = self.config.getAdminMail("global") # Nice trick, isn't it ?
258                server = smtplib.SMTP(self.smtpserver)
259                msg = MIMEText(fullmessage, _charset=self.charset)
260                msg["Subject"] = Header("PyKota v%s crash traceback !" \
261                                        % __version__, charset=self.charset)
262                msg["From"] = admin
263                msg["To"] = crashrecipient
264                msg["Cc"] = admin
265                msg["Date"] = email.Utils.formatdate(localtime=True)
266                server.sendmail(admin, [admin, crashrecipient], msg.as_string())
267                server.quit()
268        except :
269            pass
270        return fullmessage   
271       
272    def parseCommandline(self, argv, short, long, allownothing=0) :
273        """Parses the command line, controlling options."""
274        # split options in two lists: those which need an argument, those which don't need any
275        short = "%sA:" % short
276        long.append("arguments=")
277        withoutarg = []
278        witharg = []
279        lgs = len(short)
280        i = 0
281        while i < lgs :
282            ii = i + 1
283            if (ii < lgs) and (short[ii] == ':') :
284                # needs an argument
285                witharg.append(short[i])
286                ii = ii + 1 # skip the ':'
287            else :
288                # doesn't need an argument
289                withoutarg.append(short[i])
290            i = ii
291               
292        for option in long :
293            if option[-1] == '=' :
294                # needs an argument
295                witharg.append(option[:-1])
296            else :
297                # doesn't need an argument
298                withoutarg.append(option)
299       
300        # then we parse the command line
301        done = 0
302        while not done :
303            # we begin with all possible options unset
304            parsed = {}
305            for option in withoutarg + witharg :
306                parsed[option] = None
307            args = []       # to not break if something unexpected happened
308            try :
309                options, args = getopt.getopt(argv, short, long)
310                if options :
311                    for (o, v) in options :
312                        # we skip the '-' chars
313                        lgo = len(o)
314                        i = 0
315                        while (i < lgo) and (o[i] == '-') :
316                            i = i + 1
317                        o = o[i:]
318                        if o in witharg :
319                            # needs an argument : set it
320                            parsed[o] = v
321                        elif o in withoutarg :
322                            # doesn't need an argument : boolean
323                            parsed[o] = 1
324                        else :
325                            # should never occur
326                            raise PyKotaCommandLineError, "Unexpected problem when parsing command line"
327                elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
328                    self.display_usage_and_quit()
329            except getopt.error, msg :
330                raise PyKotaCommandLineError, str(msg)
331            else :   
332                if parsed["arguments"] or parsed["A"] :
333                    # arguments are in a file, we ignore all other arguments
334                    # and reset the list of arguments to the lines read from
335                    # the file.
336                    argsfile = open(parsed["arguments"] or parsed["A"], "r") # TODO : charset decoding
337                    argv = [ l.strip() for l in argsfile.readlines() ]
338                    argsfile.close()
339                    for i in range(len(argv)) :
340                        argi = argv[i]
341                        if argi.startswith('"') and argi.endswith('"') :
342                            argv[i] = argi[1:-1]
343                else :   
344                    done = 1
345        return (parsed, args)
346   
347class PyKotaTool(Tool) :   
348    """Base class for all PyKota command line tools."""
349    def __init__(self, lang="", charset=None, doc="PyKota v%(__version__)s (c) %(__years__)s %(__author__)s") :
350        """Initializes the command line tool and opens the database."""
351        Tool.__init__(self, lang, charset, doc)
352       
353    def deferredInit(self) :   
354        """Deferred initialization."""
355        Tool.deferredInit(self)
356        self.storage = storage.openConnection(self)
357        if self.config.isAdmin : # TODO : We don't know this before, fix this !
358            self.logdebug("Beware : running as a PyKota administrator !")
359        else :   
360            self.logdebug("Don't Panic : running as a mere mortal !")
361       
362    def clean(self) :   
363        """Ensures that the database is closed."""
364        try :
365            self.storage.close()
366        except (TypeError, NameError, AttributeError) :   
367            pass
368           
369    def isValidName(self, name) :
370        """Checks if a user or printer name is valid."""
371        invalidchars = "/@?*,;&|"
372        for c in list(invalidchars) :
373            if c in name :
374                return 0
375        return 1       
376       
377    def sendMessage(self, adminmail, touser, fullmessage) :
378        """Sends an email message containing headers to some user."""
379        try :   
380            server = smtplib.SMTP(self.smtpserver)
381        except socket.error, msg :   
382            self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
383        else :
384            try :
385                server.sendmail(adminmail, [touser], fullmessage)
386            except smtplib.SMTPException, answer :   
387                for (k, v) in answer.recipients.items() :
388                    self.printInfo(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
389            server.quit()
390       
391    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
392        """Sends an email message to a user."""
393        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
394        usermail = user.Email or user.Name
395        if "@" not in usermail :
396            usermail = "%s@%s" % (usermail, self.maildomain or self.smtpserver or "localhost")
397        msg = MIMEText(message, _charset=self.charset)
398        msg["Subject"] = Header(subject, charset=self.charset)
399        msg["From"] = adminmail
400        msg["To"] = usermail
401        msg["Date"] = email.Utils.formatdate(localtime=True)
402        self.sendMessage(adminmail, usermail, msg.as_string())
403       
404    def sendMessageToAdmin(self, adminmail, subject, message) :
405        """Sends an email message to the Print Quota administrator."""
406        if "@" not in adminmail :
407            adminmail = "%s@%s" % (adminmail, self.maildomain or self.smtpserver or "localhost")
408        msg = MIMEText(message, _charset=self.charset)
409        msg["Subject"] = Header(subject, charset=self.charset)
410        msg["From"] = adminmail
411        msg["To"] = adminmail
412        self.sendMessage(adminmail, adminmail, msg.as_string())
413       
414    def _checkUserPQuota(self, userpquota) :           
415        """Checks the user quota on a printer and deny or accept the job."""
416        # then we check the user's own quota
417        # if we get there we are sure that policy is not EXTERNAL
418        user = userpquota.User
419        printer = userpquota.Printer
420        enforcement = self.config.getPrinterEnforcement(printer.Name)
421        self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
422        (policy, dummy) = self.config.getPrinterPolicy(userpquota.Printer.Name)
423        if not userpquota.Exists :
424            # Unknown userquota
425            if policy == "ALLOW" :
426                action = "POLICY_ALLOW"
427            else :   
428                action = "POLICY_DENY"
429            self.printInfo(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
430        else :   
431            pagecounter = int(userpquota.PageCounter or 0)
432            if enforcement == "STRICT" :
433                pagecounter += self.softwareJobSize
434            if userpquota.SoftLimit is not None :
435                softlimit = int(userpquota.SoftLimit)
436                if pagecounter < softlimit :
437                    action = "ALLOW"
438                else :   
439                    if userpquota.HardLimit is None :
440                        # only a soft limit, this is equivalent to having only a hard limit
441                        action = "DENY"
442                    else :   
443                        hardlimit = int(userpquota.HardLimit)
444                        if softlimit <= pagecounter < hardlimit :   
445                            now = DateTime.now()
446                            if userpquota.DateLimit is not None :
447                                datelimit = DateTime.ISO.ParseDateTime(str(userpquota.DateLimit)[:19])
448                            else :
449                                datelimit = now + self.config.getGraceDelay(printer.Name)
450                                userpquota.setDateLimit(datelimit)
451                            if now < datelimit :
452                                action = "WARN"
453                            else :   
454                                action = "DENY"
455                        else :         
456                            action = "DENY"
457            else :       
458                if userpquota.HardLimit is not None :
459                    # no soft limit, only a hard one.
460                    hardlimit = int(userpquota.HardLimit)
461                    if pagecounter < hardlimit :
462                        action = "ALLOW"
463                    else :     
464                        action = "DENY"
465                else :
466                    # Both are unset, no quota, i.e. accounting only
467                    action = "ALLOW"
468        return action
469   
470    def checkGroupPQuota(self, grouppquota) :   
471        """Checks the group quota on a printer and deny or accept the job."""
472        group = grouppquota.Group
473        printer = grouppquota.Printer
474        enforcement = self.config.getPrinterEnforcement(printer.Name)
475        self.logdebug("Checking group %s's quota on printer %s" % (group.Name, printer.Name))
476        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
477            val = group.AccountBalance or 0.0
478            if enforcement == "STRICT" : 
479                val -= self.softwareJobPrice # use precomputed size.
480            balancezero = self.config.getBalanceZero()
481            if val <= balancezero :
482                action = "DENY"
483            elif val <= self.config.getPoorMan() :   
484                action = "WARN"
485            else :   
486                action = "ALLOW"
487            if (enforcement == "STRICT") and (val == balancezero) :
488                action = "WARN" # we can still print until account is 0
489        else :
490            val = grouppquota.PageCounter or 0
491            if enforcement == "STRICT" :
492                val += int(self.softwareJobSize) # TODO : this is not a fix, problem is elsewhere in grouppquota.PageCounter
493            if grouppquota.SoftLimit is not None :
494                softlimit = int(grouppquota.SoftLimit)
495                if val < softlimit :
496                    action = "ALLOW"
497                else :   
498                    if grouppquota.HardLimit is None :
499                        # only a soft limit, this is equivalent to having only a hard limit
500                        action = "DENY"
501                    else :   
502                        hardlimit = int(grouppquota.HardLimit)
503                        if softlimit <= val < hardlimit :   
504                            now = DateTime.now()
505                            if grouppquota.DateLimit is not None :
506                                datelimit = DateTime.ISO.ParseDateTime(str(grouppquota.DateLimit)[:19])
507                            else :
508                                datelimit = now + self.config.getGraceDelay(printer.Name)
509                                grouppquota.setDateLimit(datelimit)
510                            if now < datelimit :
511                                action = "WARN"
512                            else :   
513                                action = "DENY"
514                        else :         
515                            action = "DENY"
516            else :       
517                if grouppquota.HardLimit is not None :
518                    # no soft limit, only a hard one.
519                    hardlimit = int(grouppquota.HardLimit)
520                    if val < hardlimit :
521                        action = "ALLOW"
522                    else :     
523                        action = "DENY"
524                else :
525                    # Both are unset, no quota, i.e. accounting only
526                    action = "ALLOW"
527        return action
528   
529    def checkUserPQuota(self, userpquota) :
530        """Checks the user quota on a printer and all its parents and deny or accept the job."""
531        user = userpquota.User
532        printer = userpquota.Printer
533       
534        # indicates that a warning needs to be sent
535        warned = 0               
536       
537        # first we check any group the user is a member of
538        for group in self.storage.getUserGroups(user) :
539            # No need to check anything if the group is in noquota mode
540            if group.LimitBy != "noquota" :
541                grouppquota = self.storage.getGroupPQuota(group, printer)
542                # for the printer and all its parents
543                for gpquota in [ grouppquota ] + grouppquota.ParentPrintersGroupPQuota :
544                    if gpquota.Exists :
545                        action = self.checkGroupPQuota(gpquota)
546                        if action == "DENY" :
547                            return action
548                        elif action == "WARN" :   
549                            warned = 1
550                       
551        # Then we check the user's account balance
552        # if we get there we are sure that policy is not EXTERNAL
553        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
554        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
555            self.logdebug("Checking account balance for user %s" % user.Name)
556            if user.AccountBalance is None :
557                if policy == "ALLOW" :
558                    action = "POLICY_ALLOW"
559                else :   
560                    action = "POLICY_DENY"
561                self.printInfo(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
562                return action       
563            else :   
564                if user.OverCharge == 0.0 :
565                    self.printInfo(_("User %s will not be charged for printing.") % user.Name)
566                    action = "ALLOW"
567                else :
568                    val = float(user.AccountBalance or 0.0)
569                    enforcement = self.config.getPrinterEnforcement(printer.Name)
570                    if enforcement == "STRICT" : 
571                        val -= self.softwareJobPrice # use precomputed size.
572                    balancezero = self.config.getBalanceZero()   
573                    if val <= balancezero :
574                        action = "DENY"
575                    elif val <= self.config.getPoorMan() :   
576                        action = "WARN"
577                    else :
578                        action = "ALLOW"
579                    if (enforcement == "STRICT") and (val == balancezero) :
580                        action = "WARN" # we can still print until account is 0
581                return action   
582        else :
583            # Then check the user quota on current printer and all its parents.               
584            policyallowed = 0
585            for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota :               
586                action = self._checkUserPQuota(upquota)
587                if action in ("DENY", "POLICY_DENY") :
588                    return action
589                elif action == "WARN" :   
590                    warned = 1
591                elif action == "POLICY_ALLOW" :   
592                    policyallowed = 1
593            if warned :       
594                return "WARN"
595            elif policyallowed :   
596                return "POLICY_ALLOW" 
597            else :   
598                return "ALLOW"
599               
600    def externalMailTo(self, cmd, action, user, printer, message) :
601        """Warns the user with an external command."""
602        username = user.Name
603        printername = printer.Name
604        email = user.Email or user.Name
605        if "@" not in email :
606            email = "%s@%s" % (email, self.maildomain or self.smtpserver)
607        os.system(cmd % locals())
608   
609    def formatCommandLine(self, cmd, user, printer) :
610        """Executes an external command."""
611        username = user.Name
612        printername = printer.Name
613        return cmd % locals()
614       
615    def warnGroupPQuota(self, grouppquota) :
616        """Checks a group quota and send messages if quota is exceeded on current printer."""
617        group = grouppquota.Group
618        printer = grouppquota.Printer
619        admin = self.config.getAdmin(printer.Name)
620        adminmail = self.config.getAdminMail(printer.Name)
621        (mailto, arguments) = self.config.getMailTo(printer.Name)
622        if group.LimitBy in ("noquota", "nochange") :
623            action = "ALLOW"
624        else :   
625            action = self.checkGroupPQuota(grouppquota)
626            if action.startswith("POLICY_") :
627                action = action[7:]
628            if action == "DENY" :
629                adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
630                self.printInfo(adminmessage)
631                if mailto in [ "BOTH", "ADMIN" ] :
632                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
633                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
634                    for user in self.storage.getGroupMembers(group) :
635                        if mailto != "EXTERNAL" :
636                            self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
637                        else :   
638                            self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printer.Name))
639            elif action == "WARN" :   
640                adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
641                self.printInfo(adminmessage)
642                if mailto in [ "BOTH", "ADMIN" ] :
643                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
644                if group.LimitBy and (group.LimitBy.lower() == "balance") : 
645                    message = self.config.getPoorWarn()
646                else :     
647                    message = self.config.getSoftWarn(printer.Name)
648                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
649                    for user in self.storage.getGroupMembers(group) :
650                        if mailto != "EXTERNAL" :
651                            self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
652                        else :   
653                            self.externalMailTo(arguments, action, user, printer, message)
654        return action       
655       
656    def warnUserPQuota(self, userpquota) :
657        """Checks a user quota and send him a message if quota is exceeded on current printer."""
658        user = userpquota.User
659        printer = userpquota.Printer
660        admin = self.config.getAdmin(printer.Name)
661        adminmail = self.config.getAdminMail(printer.Name)
662        (mailto, arguments) = self.config.getMailTo(printer.Name)
663       
664        if user.LimitBy in ("noquota", "nochange") :
665            action = "ALLOW"
666        elif user.LimitBy == "noprint" :
667            action = "DENY"
668            message = _("User %s is not allowed to print at this time.") % user.Name
669            self.printInfo(message)
670            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
671                if mailto != "EXTERNAL" :
672                    self.sendMessageToUser(admin, adminmail, user, _("Printing denied."), message)
673                else :   
674                    self.externalMailTo(arguments, action, user, printer, message)
675            if mailto in [ "BOTH", "ADMIN" ] :
676                self.sendMessageToAdmin(adminmail, _("Print Quota"), message)
677        else :
678            action = self.checkUserPQuota(userpquota)
679            if action.startswith("POLICY_") :
680                action = action[7:]
681               
682            if action == "DENY" :
683                adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
684                self.printInfo(adminmessage)
685                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
686                    message = self.config.getHardWarn(printer.Name)
687                    if mailto != "EXTERNAL" :
688                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
689                    else :   
690                        self.externalMailTo(arguments, action, user, printer, message)
691                if mailto in [ "BOTH", "ADMIN" ] :
692                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
693            elif action == "WARN" :   
694                adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
695                self.printInfo(adminmessage)
696                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
697                    if user.LimitBy and (user.LimitBy.lower() == "balance") : 
698                        message = self.config.getPoorWarn()
699                    else :     
700                        message = self.config.getSoftWarn(printer.Name)
701                    if mailto != "EXTERNAL" :   
702                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
703                    else :   
704                        self.externalMailTo(arguments, action, user, printer, message)
705                if mailto in [ "BOTH", "ADMIN" ] :
706                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
707        return action       
708       
Note: See TracBrowser for help on using the browser.