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

Revision 3286, 34.5 kB (checked in by jerome, 16 years ago)

Typo

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