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

Revision 1218, 28.9 kB (checked in by jalet, 20 years ago)

Small code move

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