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

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

No more message on stderr when the translation is not available.

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