#! /usr/bin/env python # PyKota accounting filter # # PyKota - Print Quotas for CUPS # # (c) 2003 Jerome Alet # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # # $Id$ # # $Log$ # Revision 1.20 2003/04/12 16:58:28 jalet # The workaround for HP printers was not correct, and there's probably no # correct way to workaround the problem because we can't save the internal # page counter in real time. The last job's size is unconditionnally set to # 5 pages in this case. # # Revision 1.19 2003/04/11 08:56:49 jalet # Comment # # Revision 1.18 2003/04/11 08:50:39 jalet # Workaround for the HP "feature" of saving the page counter to NVRAM # only every time 10 new pages are printed... # Workaround for printers with volatile page counters. # # Revision 1.17 2003/04/10 21:47:20 jalet # Job history added. Upgrade script neutralized for now ! # # Revision 1.16 2003/04/08 20:38:08 jalet # The last job Id is saved now for each printer, this will probably # allow other accounting methods in the future. # # Revision 1.15 2003/03/29 13:45:27 jalet # GPL paragraphs were incorrectly (from memory) copied into the sources. # Two README files were added. # Upgrade script for PostgreSQL pre 1.01 schema was added. # # Revision 1.14 2003/03/07 22:16:57 jalet # Algorithmically incorrect : last user quota wasn't updated if current # user wasn't allowed to print. # # Revision 1.13 2003/02/27 23:59:28 jalet # Stupid bug wrt exception handlingand value conversion # # Revision 1.12 2003/02/27 23:48:41 jalet # Correctly maps PyKota's log levels to syslog log levels # # Revision 1.11 2003/02/27 22:55:20 jalet # WARN log priority doesn't exist. # # Revision 1.10 2003/02/27 22:43:21 jalet # Missing import # # Revision 1.9 2003/02/27 22:40:26 jalet # Correctly handles cases where the printer is off. # # Revision 1.8 2003/02/09 12:56:53 jalet # Internationalization begins... # # Revision 1.7 2003/02/07 10:23:48 jalet # Avoid a possible future name clash # # Revision 1.6 2003/02/06 22:54:33 jalet # warnpykota should be ok # # Revision 1.5 2003/02/05 22:45:25 jalet # Forgotten import # # Revision 1.4 2003/02/05 22:42:51 jalet # Typo # # Revision 1.3 2003/02/05 22:38:39 jalet # Typo # # Revision 1.2 2003/02/05 22:16:20 jalet # DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools # # Revision 1.1 2003/02/05 21:28:17 jalet # Initial import into CVS # # # import sys import os from pykota.tool import PyKotaTool, PyKotaToolError from pykota.requester import openRequester, PyKotaRequesterError class PyKotaFilter(PyKotaTool) : """Class for the PyKota filter.""" def __init__(self, username) : PyKotaTool.__init__(self, isfilter=1) self.username = username self.requester = openRequester(self.config, self.printername) self.printerhostname = self.getPrinterHostname() def getPrinterHostname(self) : """Returns the printer hostname.""" device_uri = os.environ.get("DEVICE_URI", "") # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp try : (backend, destination) = device_uri.split(":", 1) except ValueError : raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri while destination.startswith("/") : destination = destination[1:] return destination.split("/")[0].split(":")[0] def filterInput(self, inputfile) : """Transparent filter.""" mustclose = 0 if inputfile is not None : infile = open(inputfile, "rb") mustclose = 1 else : infile = sys.stdin data = infile.read(256*1024) while data : sys.stdout.write(data) data = infile.read(256*1024) if mustclose : infile.close() def main() : """Do it, and do it right !""" # # This is a CUPS filter, so we should act and die like a CUPS filter when needed narg = len(sys.argv) if narg not in (6, 7) : sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0]) return 1 elif narg == 7 : # input file inputfile = sys.argv[6] else : # stdin inputfile = None # # According to CUPS documentation, the job id is the second command line argument jobid = sys.argv[1].strip() # # According to CUPS documentation, the username is the third command line argument username = sys.argv[2].strip() # Initializes the current tool kotafilter = PyKotaFilter(username) # Get the page counter directly from the printer itself try : counterbeforejob = kotafilter.requester.getPrinterPageCounter(kotafilter.printerhostname) # TODO use printername instead, make them match from CUPS' config files except PyKotaRequesterError, msg : # can't get actual page counter, assume printer is off, but warns in log kotafilter.logger.log_message("%s" % msg, "warn") counterbeforejob = None printerIsOff = 1 else : printerIsOff = 0 # Get the last page counter and last username from the Quota Storage backend printerid = kotafilter.storage.getPrinterId(kotafilter.printername) if printerid is None : # The printer is unknown from the Quota Storage perspective # we let the job pass through, but log a warning message kotafilter.logger.log_message(_("Printer %s not registered in the PyKota system") % kotafilter.printername, "warn") else : userid = kotafilter.storage.getUserId(username) if userid is None : # The user is unknown from the Quota Storage perspective # we let the job pass through, but log a warning message kotafilter.logger.log_message(_("User %s not registered in the PyKota system") % username, "warn") else : # get last job information for this printer pgc = kotafilter.storage.getPrinterPageCounter(printerid) if pgc is None : # The printer hasn't been used yet, from PyKota's point of view lasthistoryid = None lastjobid = jobid lastuserid = userid lastusername = username lastpagecounter = counterbeforejob else : # get last values from Quota Storage (lasthistoryid, lastjobid, lastuserid, lastusername, lastpagecounter) = (pgc["id"], pgc["jobid"], pgc["userid"], pgc["username"], pgc["pagecounter"]) # if printer is off then we assume the correct counter value is the last one if printerIsOff : counterbeforejob = lastpagecounter # if the internal lifetime page counter for this printer is 0 # then this may be a printer with a volatile counter (never # saved to NVRAM) which has just been switched off and then on # so we use the last page counter from the Quota Storage instead # explanation at : http://web.mit.edu/source/third/lprng/doc/LPRng-HOWTO-15.html if counterbeforejob == 0 : counterbeforejob = lastpagecounter # Computes the last job size as the difference between internal page # counter in the printer and last page counter taken from the Quota # Storage database for this particular printer jobsize = (counterbeforejob - lastpagecounter) if jobsize < 0 : # Probably an HP printer which was switched off and back on, # its primary counter is only saved in a 10 increment, so # it may be lower than the last page counter saved in the # Quota Storage. We unconditionnally set 5 (five) pages # as the last job's size. This is a mean. For more accurate # accounting, don't switch off your HP printers ! # explanation at : http://web.mit.edu/source/third/lprng/doc/LPRng-HOWTO-15.html kotafilter.logger.log_message(_("Error in page count value %i for user %s on printer %s") % (jobsize, lastusername, kotafilter.printername), "error") jobsize = 5 # Workaround for HP printers' feature ! # update the quota for the previous user on this printer kotafilter.storage.updateUserPQuota(lastuserid, printerid, jobsize) # update the last job size in the history kotafilter.storage.updateJobSizeInHistory(lasthistoryid, jobsize) # warns the last user if he is over quota kotafilter.warnUserPQuota(lastusername) # Is the current user allowed to print at all ? action = kotafilter.warnUserPQuota(username) # adds the current job to history kotafilter.storage.addJobToHistory(jobid, kotafilter.storage.getUserId(username), printerid, counterbeforejob, action) # if not allowed to print then die, else proceed. if action == "DENY" : # No, just die cleanly return 1 # pass the job untouched to the underlying layer kotafilter.filterInput(inputfile) return 0 if __name__ == "__main__" : sys.exit(main() or 0)