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

Revision 1211, 28.6 kB (checked in by jalet, 20 years ago)

Puts 'root' instead of when printing from CUPS web interface (which
gives an empty username)

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