root / pykota / trunk / bin / cupspykota @ 1178

Revision 1178, 12.6 kB (checked in by jalet, 20 years ago)

New CUPS backend supports device enumeration

  • 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.2  2003/11/12 09:33:34  jalet
27# New CUPS backend supports device enumeration
28#
29# Revision 1.1  2003/11/08 16:05:31  jalet
30# CUPS backend added for people to experiment.
31#
32#
33#
34
35import sys
36import os
37import time
38import cStringIO
39import shlex
40
41from pykota.tool import PyKotaTool, PyKotaToolError
42from pykota.config import PyKotaConfigError
43from pykota.storage import PyKotaStorageError
44from pykota.accounter import openAccounter, PyKotaAccounterError
45from pykota.requester import openRequester, PyKotaRequesterError
46
47class PyKotaBackend(PyKotaTool) :   
48    """Class for the PyKota backend."""
49    def __init__(self) :
50        PyKotaTool.__init__(self)
51        (self.printingsystem, \
52         self.printerhostname, \
53         self.printername, \
54         self.username, \
55         self.jobid, \
56         self.inputfile, \
57         self.copies, \
58         self.title, \
59         self.options, \
60         self.originalbackend) = self.extractCUPSInfo()
61        self.accounter = openAccounter(self)
62   
63    def extractCUPSInfo(self) :   
64        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
65       
66           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
67        """
68        # Try to detect CUPS
69        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
70            if len(sys.argv) == 7 :
71                inputfile = sys.argv[6]
72            else :   
73                inputfile = None
74               
75            # the DEVICE_URI environment variable's value is
76            # prefixed with "cupspykota:" otherwise we wouldn't
77            # be called. We have to remove this from the environment
78            # before launching the real backend.
79            fulldevice_uri = os.environ.get("DEVICE_URI", "")
80            device_uri = fulldevice_uri[len("cupspykota:"):]
81            os.environ["DEVICE_URI"] = device_uri
82            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
83            try :
84                (backend, destination) = device_uri.split(":", 1) 
85            except ValueError :   
86                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
87            while destination.startswith("/") :
88                destination = destination[1:]
89            printerhostname = destination.split("/")[0].split(":")[0]
90            return ("CUPS", \
91                    printerhostname, \
92                    os.environ.get("PRINTER"), \
93                    sys.argv[2].strip(), \
94                    sys.argv[1].strip(), \
95                    inputfile, \
96                    int(sys.argv[4].strip()), \
97                    sys.argv[3], \
98                    sys.argv[5], \
99                    backend)
100        else :   
101            self.logger.log_message(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
102            return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system         
103       
104def format_commandline(prt, usr, cmdline) :           
105    """Passes printer and user names on the command line."""
106    printer = prt.Name
107    user = usr.Name
108    # we don't want the external command's standard
109    # output to break the print job's data, but we
110    # want to keep its standard error
111    return "%s >/dev/null" % (cmdline % locals())
112
113def main(thebackend) :   
114    """Do it, and do it right !"""
115    #
116    # Get the last page counter and last username from the Quota Storage backend
117    printer = thebackend.storage.getPrinter(thebackend.printername)
118    if not printer.Exists :
119        # The printer is unknown from the Quota Storage perspective
120        # we let the job pass through, but log a warning message
121        thebackend.logger.log_message(_("Printer %s not registered in the PyKota system") % thebackend.printername, "warn")
122    else :   
123        for dummy in range(2) :
124            user = thebackend.storage.getUser(thebackend.username)
125            if user.Exists :
126                break
127            else :   
128                # The user is unknown from the Quota Storage perspective
129                # Depending on the default policy for this printer, we
130                # either let the job pass through or reject it, but we
131                # log a message in any case.
132                (policy, args) = thebackend.config.getPrinterPolicy(thebackend.printername)
133                if policy == "ALLOW" :
134                    action = "POLICY_ALLOW"
135                elif policy == "EXTERNAL" :   
136                    commandline = format_commandline(printer, user, args)
137                    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")
138                    if os.system(commandline) :
139                        # if an error occured, we die without error,
140                        # so that the job doesn't stop the print queue.
141                        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")
142                        return 0
143                    else :   
144                        # here we try a second time, because the goal
145                        # of the external action was to add the user
146                        # in the database.
147                        continue 
148                else :   
149                    action = "POLICY_DENY"
150                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")
151                if action == "POLICY_DENY" :
152                    # if not allowed to print then die, else proceed.
153                    # we die without error, so that the job doesn't
154                    # stop the print queue.
155                    return 0
156                # when we get there, the printer policy allows the job to pass
157                break   
158                   
159        # if user exists, do accounting
160        if user.Exists :
161            # Is the current user allowed to print at all ?
162            action = thebackend.warnUserPQuota(thebackend.storage.getUserPQuota(user, printer))
163        elif policy == "EXTERNAL" :               
164            # if the extenal policy produced no error, but the
165            # user still doesn't exist, we die without error,
166            # so that the job doesn't stop the print queue.
167            thebackend.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thebackend.printername, thebackend.username), "error")
168            return 0
169       
170    MAXTRIES = 12    # maximum number of tries to get the printer's internal page counter
171    TIMETOSLEEP = 10 # number of seconds to sleep between two tries to get the printer's internal page counter
172    requester = openRequester(thebackend.config, thebackend.printername)
173    def getPPC(requester, backend) :
174        for i in range(MAXTRIES) :
175            try :
176                counterbeforejob = requester.getPrinterPageCounter(backend.printerhostname)
177            except PyKotaRequesterError, msg :
178                # can't get actual page counter, assume printer is off or warming up
179                # log the message anyway.
180                backend.logger.log_message("%s" % msg, "warn")
181                counterbeforejob = None
182            else :   
183                # printer answered, it is on so we can exit the loop
184                break
185            time.sleep(TIMETOSLEEP)   
186        return counterbeforejob   
187       
188    if action not in ["ALLOW", "WARN"] :   
189        # if not allowed to print then die, else proceed.
190        # we die without error, so that the job doesn't
191        # stop the print queue.
192        retcode = 0
193    else :
194        # pass the job untouched to the underlying layer
195        # but get printer page counter before and after
196        # print job is submitted to the hardware.
197       
198        # get page counter before job
199        before = getPPC(requester, thebackend)
200       
201        # executes backend
202        # TODO : use correct original backend.
203        realbackend = os.path.join(os.path.split(sys.argv[0])[0], thebackend.originalbackend)
204        retcode = os.spawnve(os.P_WAIT, realbackend, [os.environ["DEVICE_URI"]] + sys.argv[1:], os.environ)
205   
206    # get page counter after job
207    after = getPPC(requester, thebackend)
208   
209    # Computes the last job size as the difference between internal page
210    # counter in the printer and last page counter taken from the Quota
211    # Storage database for this particular printer
212    try :
213        jobsize = (after - before)   
214    except :   
215        jobsize = 0
216       
217    # update the quota for the current user on this printer
218    if printer.Exists :
219        if jobsize :
220            userquota = thebackend.storage.getUserPQuota(user, printer)
221            if userquota.Exists :
222                userquota.increasePagesUsage(jobsize)
223       
224        # adds the current job to history   
225        printer.addJobToHistory(thebackend.jobid, user, after, action, jobsize)
226   
227    return retcode
228
229if __name__ == "__main__" :   
230    # This is a CUPS backend, we should act and die like a CUPS backend
231    if len(sys.argv) == 1 :
232        # we will execute each existing backend in device enumeration mode
233        # and generate their PyKota accounting counterpart
234        (directory, myname) = os.path.split(sys.argv[0])
235        for backend in [os.path.join(directory, b) for b in os.listdir(directory) if os.path.isfile(os.path.join(directory, b)) and (b != myname)] :
236            answer = os.popen(backend, "r")
237            try :
238                devices = [line.strip() for line in answer.readlines()]
239            except :   
240                devices = []
241            status = answer.close()
242            if status is None :
243                for d in devices :
244                    fdevice = cStringIO.StringIO("%s" % d)
245                    tokenizer = shlex.shlex(fdevice)
246                    tokenizer.wordchars = tokenizer.wordchars + r".:,?!~/\_$*-+={}[]()#"
247                    arguments = []
248                    while 1 :
249                        token = tokenizer.get_token()
250                        if token :
251                            arguments.append(token)
252                        else :
253                            break
254                    fdevice.close()
255                    (devicetype, device, name, fullname) = arguments
256                    if name.startswith('"') and name.endswith('"') :
257                        name = name[1:-1]
258                    if fullname.startswith('"') and fullname.endswith('"') :
259                        fullname = fullname[1:-1]
260                    print '%s cupspykota:%s "PyKota+%s" "PyKota managed %s"' % (devicetype, device, name, fullname)   
261        retcode = 0
262    elif len(sys.argv) not in (6, 7) :   
263        sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
264        retcode = 1
265    else :   
266        try :
267            # Initializes the backend
268            kotabackend = PyKotaBackend()   
269            retcode = main(kotabackend)
270        except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
271            sys.stderr.write("ERROR : cupspykota backend failed (%s)\n" % msg)
272            sys.stderr.flush()
273            retcode = 1
274       
275        try :
276            kotabackend.storage.close()
277        except (TypeError, NameError, AttributeError) :   
278            pass
279       
280    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.