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

Revision 1216, 28.7 kB (checked in by jalet, 20 years ago)

updated FAQ

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