root / pykota / trunk / bin / cupspykota @ 1177

Revision 1177, 11.0 kB (checked in by jalet, 21 years ago)

CUPS backend added for people to experiment.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# CUPSPyKota accounting backend
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22#
23# $Id$
24#
25# $Log$
26# Revision 1.1  2003/11/08 16:05:31  jalet
27# CUPS backend added for people to experiment.
28#
29#
30#
31
32import sys
33import os
34import time
35
36from pykota.tool import PyKotaTool, PyKotaToolError
37from pykota.config import PyKotaConfigError
38from pykota.storage import PyKotaStorageError
39from pykota.accounter import openAccounter, PyKotaAccounterError
40from pykota.requester import openRequester, PyKotaRequesterError
41
42class PyKotaBackend(PyKotaTool) :   
43    """Class for the PyKota backend."""
44    def __init__(self) :
45        PyKotaTool.__init__(self)
46        (self.printingsystem, \
47         self.printerhostname, \
48         self.printername, \
49         self.username, \
50         self.jobid, \
51         self.inputfile, \
52         self.copies, \
53         self.title, \
54         self.options, \
55         self.originalbackend) = self.extractCUPSInfo()
56        self.accounter = openAccounter(self)
57   
58    def extractCUPSInfo(self) :   
59        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
60       
61           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
62        """
63        # Try to detect CUPS
64        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
65            if len(sys.argv) == 7 :
66                inputfile = sys.argv[6]
67            else :   
68                inputfile = None
69               
70            # the DEVICE_URI environment variable's value is
71            # prefixed with "cupspykota:" otherwise we wouldn't
72            # be called. We have to remove this from the environment
73            # before launching the real backend.
74            fulldevice_uri = os.environ.get("DEVICE_URI", "")
75            device_uri = fulldevice_uri[len("cupspykota:"):]
76            os.environ["DEVICE_URI"] = device_uri
77            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
78            try :
79                (backend, destination) = device_uri.split(":", 1) 
80            except ValueError :   
81                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
82            while destination.startswith("/") :
83                destination = destination[1:]
84            printerhostname = destination.split("/")[0].split(":")[0]
85            return ("CUPS", \
86                    printerhostname, \
87                    os.environ.get("PRINTER"), \
88                    sys.argv[2].strip(), \
89                    sys.argv[1].strip(), \
90                    inputfile, \
91                    int(sys.argv[4].strip()), \
92                    sys.argv[3], \
93                    sys.argv[5], \
94                    backend)
95        else :   
96            self.logger.log_message(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
97            return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system         
98       
99def format_commandline(prt, usr, cmdline) :           
100    """Passes printer and user names on the command line."""
101    printer = prt.Name
102    user = usr.Name
103    # we don't want the external command's standard
104    # output to break the print job's data, but we
105    # want to keep its standard error
106    return "%s >/dev/null" % (cmdline % locals())
107
108def main(thebackend) :   
109    """Do it, and do it right !"""
110    #
111    # Get the last page counter and last username from the Quota Storage backend
112    printer = thebackend.storage.getPrinter(thebackend.printername)
113    if not printer.Exists :
114        # The printer is unknown from the Quota Storage perspective
115        # we let the job pass through, but log a warning message
116        thebackend.logger.log_message(_("Printer %s not registered in the PyKota system") % thebackend.printername, "warn")
117    else :   
118        for dummy in range(2) :
119            user = thebackend.storage.getUser(thebackend.username)
120            if user.Exists :
121                break
122            else :   
123                # The user is unknown from the Quota Storage perspective
124                # Depending on the default policy for this printer, we
125                # either let the job pass through or reject it, but we
126                # log a message in any case.
127                (policy, args) = thebackend.config.getPrinterPolicy(thebackend.printername)
128                if policy == "ALLOW" :
129                    action = "POLICY_ALLOW"
130                elif policy == "EXTERNAL" :   
131                    commandline = format_commandline(printer, user, args)
132                    thebackend.logger.log_message(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (thebackend.username, commandline, thebackend.printername), "info")
133                    if os.system(commandline) :
134                        # if an error occured, we die without error,
135                        # so that the job doesn't stop the print queue.
136                        thebackend.logger.log_message(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, thebackend.printername), "error")
137                        return 0
138                    else :   
139                        # here we try a second time, because the goal
140                        # of the external action was to add the user
141                        # in the database.
142                        continue 
143                else :   
144                    action = "POLICY_DENY"
145                thebackend.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thebackend.username, action, thebackend.printername), "warn")
146                if action == "POLICY_DENY" :
147                    # if not allowed to print then die, else proceed.
148                    # we die without error, so that the job doesn't
149                    # stop the print queue.
150                    return 0
151                # when we get there, the printer policy allows the job to pass
152                break   
153                   
154        # if user exists, do accounting
155        if user.Exists :
156            # Is the current user allowed to print at all ?
157            action = thebackend.warnUserPQuota(thebackend.storage.getUserPQuota(user, printer))
158        elif policy == "EXTERNAL" :               
159            # if the extenal policy produced no error, but the
160            # user still doesn't exist, we die without error,
161            # so that the job doesn't stop the print queue.
162            thebackend.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thebackend.printername, thebackend.username), "error")
163            return 0
164       
165    MAXTRIES = 12    # maximum number of tries to get the printer's internal page counter
166    TIMETOSLEEP = 10 # number of seconds to sleep between two tries to get the printer's internal page counter
167    requester = openRequester(thebackend.config, thebackend.printername)
168    def getPPC(requester, backend) :
169        for i in range(MAXTRIES) :
170            try :
171                counterbeforejob = requester.getPrinterPageCounter(backend.printerhostname)
172            except PyKotaRequesterError, msg :
173                # can't get actual page counter, assume printer is off or warming up
174                # log the message anyway.
175                backend.logger.log_message("%s" % msg, "warn")
176                counterbeforejob = None
177            else :   
178                # printer answered, it is on so we can exit the loop
179                break
180            time.sleep(TIMETOSLEEP)   
181        return counterbeforejob   
182       
183    if action not in ["ALLOW", "WARN"] :   
184        # if not allowed to print then die, else proceed.
185        # we die without error, so that the job doesn't
186        # stop the print queue.
187        retcode = 0
188    else :
189        # pass the job untouched to the underlying layer
190        # but get printer page counter before and after
191        # print job is submitted to the hardware.
192       
193        # get page counter before job
194        before = getPPC(requester, thebackend)
195       
196        # executes backend
197        # TODO : use correct original backend.
198        realbackend = os.path.join(os.path.split(sys.argv[0])[0], thebackend.originalbackend)
199        retcode = os.spawnve(os.P_WAIT, realbackend, [os.environ["DEVICE_URI"]] + sys.argv[1:], os.environ)
200   
201    # get page counter after job
202    after = getPPC(requester, thebackend)
203   
204    # Computes the last job size as the difference between internal page
205    # counter in the printer and last page counter taken from the Quota
206    # Storage database for this particular printer
207    try :
208        jobsize = (after - before)   
209    except :   
210        jobsize = 0
211       
212    # update the quota for the current user on this printer
213    if printer.Exists :
214        if jobsize :
215            userquota = thebackend.storage.getUserPQuota(user, printer)
216            if userquota.Exists :
217                userquota.increasePagesUsage(jobsize)
218       
219        # adds the current job to history   
220        printer.addJobToHistory(thebackend.jobid, user, after, action, jobsize)
221   
222    return retcode
223
224if __name__ == "__main__" :   
225    # This is a CUPS backend, we should act and die like a CUPS backend
226    if len(sys.argv) == 1 :
227        print 'direct cupspykota "PyKota" "Print Quota and Accounting Backend"' 
228        retcode = 0
229    elif len(sys.argv) not in (6, 7) :   
230        sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
231        retcode = 1
232    else :   
233        try :
234            # Initializes the backend
235            kotabackend = PyKotaBackend()   
236            retcode = main(kotabackend)
237        except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
238            sys.stderr.write("ERROR : cupspykota backend failed (%s)\n" % msg)
239            sys.stderr.flush()
240            retcode = 1
241       
242        try :
243            kotabackend.storage.close()
244        except (TypeError, NameError, AttributeError) :   
245            pass
246       
247    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.