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

Revision 3287, 33.3 kB (checked in by jerome, 16 years ago)

Removed the dependency on python-chardet entirely. Now uses unicode
or UTF-8 strings all over the place : UTF-8 still used for datas
coming from/going to the database (TODO). Conversion to end user's
locale charset is now only done when needed. PyKota Exceptions need
a base class which, for now, will handle the charset translation,
until we get the time to replace internal loggers with Python's
logging module...

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