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

Revision 1200, 28.5 kB (checked in by jalet, 20 years ago)

More complete job history.

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