#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # PyKota accounting filter # # PyKota - Print Quotas for CUPS and LPRng # # (c) 2003-2004 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.59 2004/05/21 21:02:56 jalet # The pykota filter is now deprecated # # Revision 1.58 2004/05/18 14:48:47 jalet # Big code changes to completely remove the need for "requester" directives, # jsut use "hardware(... your previous requester directive's content ...)" # # Revision 1.57 2004/03/15 10:47:56 jalet # This time the traceback formatting should be correct ! # # Revision 1.56 2004/03/05 12:46:08 jalet # Improve tracebacks # # Revision 1.55 2004/03/05 12:31:35 jalet # Now should output full traceback when crashing # # Revision 1.54 2004/03/01 15:06:51 jalet # Pre and Post hooks should now work in the pykota filter too. # The pykota filter doesn't check the last user's quota anymore # when delayed hardware accounting is used : this will be checked # anyway the next time the last user will print # # Revision 1.53 2004/02/25 12:36:34 jalet # Avoids a database query even if caching was disabled. # # Revision 1.52 2004/01/14 15:57:54 jalet # Missing return caused problems with LPRng # # Revision 1.51 2004/01/12 18:20:45 jalet # Denied jobs weren't added to the history anymore, this is now fixed. # # Revision 1.50 2004/01/11 23:22:42 jalet # Major code refactoring, it's way cleaner, and now allows automated addition # of printers on first print. # # Revision 1.49 2004/01/08 14:10:32 jalet # Copyright year changed. # # Revision 1.48 2003/12/27 16:49:25 uid67467 # Should be ok now. # # Revision 1.47 2003/11/26 19:17:35 jalet # Printing on a printer not present in the Quota Storage now results # in the job being stopped or cancelled depending on the system. # # Revision 1.46 2003/11/24 14:25:02 jalet # Missing import in pykota filter # # Revision 1.45 2003/11/19 23:19:37 jalet # Code refactoring work. # Explicit redirection to /dev/null has to be set in external policy now, just # like in external mailto. # # Revision 1.44 2003/11/08 16:05:31 jalet # CUPS backend added for people to experiment. # # Revision 1.43 2003/11/06 22:33:25 jalet # French variable name # # Revision 1.42 2003/10/08 21:41:38 jalet # External policies for printers works ! # We can now auto-add users on first print, and do other useful things if needed. # # Revision 1.41 2003/10/07 09:07:27 jalet # Character encoding added to please latest version of Python # # Revision 1.40 2003/09/04 08:38:56 jalet # Added an exception catch to ensure clean close of database even in # case of TypeError too. # # Revision 1.39 2003/08/18 16:35:28 jalet # New pychecker pass, on the tools this time. # # Revision 1.38 2003/08/18 16:20:59 jalet # Improvement of the printing system detection code. # # Revision 1.37 2003/07/29 20:55:17 jalet # 1.14 is out ! # # Revision 1.36 2003/07/10 06:09:52 jalet # Incorrect documentation string # # Revision 1.35 2003/06/25 14:10:01 jalet # Hey, it may work (edpykota --reset excepted) ! # # Revision 1.34 2003/06/13 18:54:17 jalet # Bug with remote jobs and LPRng fixed. # # Revision 1.33 2003/05/28 13:51:38 jalet # Better handling of errors # # Revision 1.32 2003/05/27 23:00:20 jalet # Big rewrite of external accounting methods. # Should work well now. # # Revision 1.31 2003/04/30 13:36:39 jalet # Stupid accounting method was added. # # Revision 1.30 2003/04/29 22:03:38 jalet # Better error handling. # # Revision 1.29 2003/04/29 18:37:54 jalet # Pluggable accounting methods (actually doesn't support external scripts) # # Revision 1.28 2003/04/26 08:41:24 jalet # Small code reorganisation (UNTESTED) to allow pluggable accounting # methods in the future. # # Revision 1.27 2003/04/25 09:23:47 jalet # Debug message passed through ! # # Revision 1.26 2003/04/25 08:23:23 jalet # Multiple tries to get the printer's internal page counter, waits for # one minute maximum for the printer to warm up, actually. # # Revision 1.25 2003/04/24 11:53:48 jalet # Default policy for unknown users/groups is to DENY printing instead # of the previous default to ALLOW printing. This is to solve an accuracy # problem. If you set the policy to ALLOW, jobs printed by in nexistant user # (from PyKota's POV) will be charged to the next user who prints on the # same printer. # # Revision 1.24 2003/04/23 22:13:56 jalet # Preliminary support for LPRng added BUT STILL UNTESTED. # # Revision 1.23 2003/04/15 11:30:57 jalet # More work done on money print charging. # Minor bugs corrected. # All tools now access to the storage as priviledged users, repykota excepted. # # Revision 1.22 2003/04/15 11:09:04 jalet # Small bug was fixed when a printer was never used and its internal # page counter is not accessible. # # Revision 1.21 2003/04/12 17:20:14 jalet # Better formula for HP workaround # # 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 PyKotaFilterOrBackend, PyKotaToolError from pykota.config import PyKotaConfigError from pykota.storage import PyKotaStorageError from pykota.accounter import PyKotaAccounterError class PyKotaFilter(PyKotaFilterOrBackend) : """A class for the pykota filter.""" def acceptJob(self) : """Returns the exit code needed by the printing backend to accept the job and print it.""" if self.printingsystem == "CUPS" : return 0 elif self.printingsystem == "LPRNG" : return 0 else : # UNKNOWN return -1 def removeJob(self) : """Returns the exit code needed by the printing backend to refuse the job and remove it.""" if self.printingsystem == "CUPS" : return 1 elif self.printingsystem == "LPRNG" : return 3 else : # UNKNOWN return -1 def doWork(self, policy, printer, user, userpquota) : """Most of the work is done here.""" # Two different values possible for policy here : # ALLOW means : Either printer, user or user print quota doesn't exist, # but the job should be allowed anyway. # OK means : Both printer, user and user print quota exist, job should # be allowed if current user is allowed to print on this printer if policy == "OK" : # exports user information with initial values self.exportUserInfo(userpquota) # enters first phase os.putenv("PYKOTAPHASE", "BEFORE") self.logdebug("Does accounting for user %s on printer %s." % (user.Name, printer.Name)) action = self.accounter.doAccounting(userpquota) # exports some new environment variables os.putenv("PYKOTAACTION", action) # launches the pre hook self.prehook(userpquota) else : action = "ALLOW" # pass the job's data to the next filter if action in ["ALLOW", "WARN"] : self.logdebug("Passing input data to next filter.") mustclose = 0 if self.inputfile is not None : if hasattr(self.inputfile, "read") : infile = self.inputfile else : infile = open(self.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() return self.acceptJob() else : self.logdebug("Printing is denied.") return self.removeJob() def mainWork(self) : # If this is a CUPS filter, we should act and die like a CUPS filter when needed if self.printingsystem == "CUPS" : if len(sys.argv) not in (6, 7) : sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0]) return self.removeJob() if self.accounter.isDelayed : # Here we need to update the last user's print quota # because hardware accounting is delayed by one print job # in the pykota filter. printer = self.storage.getPrinter(self.printername) if not printer.Exists : self.logger.log_message(_("Printer %s not registered in the PyKota system") % self.printername, "info") else : if not printer.LastJob.Exists : self.logger.log_message(_("Printer %s was never used") % self.printername, "info") else : self.logdebug("Updating print quota for last user %s on printer %s" % (printer.LastJob.User.Name, printer.Name)) lastuserpquota = self.storage.getUserPQuota(printer.LastJob.User, printer) self.accounter.counterbefore = printer.LastJob.PrinterPageCounter self.accounter.counterafter = self.accounter.getPrinterInternalPageCounter() or 0 lastjobsize = self.accounter.getJobSize() self.logdebug("Last Job size : %i" % lastjobsize) lastjobprice = printer.LastJob.setSize(lastuserpquota, lastjobsize) self.logdebug("Updating user %s's quota on printer %s" % (lastuserpquota.User.Name, printer.Name)) lastuserpquota.increasePagesUsage(lastjobsize) # exports user information with final values self.exportUserInfo(lastuserpquota) # enters final phase os.putenv("PYKOTAPHASE", "AFTER") os.putenv("PYKOTAACTION", printer.LastJob.JobAction) os.putenv("PYKOTAJOBSIZE", str(lastjobsize)) os.putenv("PYKOTAJOBPRICE", str(lastjobprice)) # launches the post hook self.posthook(lastuserpquota) # Code below deactivated since this will be checked # anyway the next time this user prints # finally check last user's quota # self.warnUserPQuota(lastuserpquota) # then deal with current print job as usual return PyKotaFilterOrBackend.mainWork(self) if __name__ == "__main__" : sys.stderr.write("ERROR : pykota is now deprecated. With CUPS use cupspykota instead.\n") sys.stderr.write("ERROR : pykota is now deprecated. With LPRng please wait for a new version.\n") sys.stderr.flush() sys.exit(-1) ############## # # # DEPRECATED # # # ############## retcode = -1 try : # Initializes the current tool kotafilter = PyKotaFilter() retcode = kotafilter.mainWork() except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg : import traceback mm = [((f.endswith('\n') and f) or (f + '\n')) for f in traceback.format_exception(*sys.exc_info())] sys.stderr.write("ERROR : pykota filter failed (%s)\n%s" % (msg, "ERROR : ".join(mm))) sys.stderr.flush() try : retcode = kotafilter.removeJob() except : retcode = -1 try : kotafilter.storage.close() except (TypeError, NameError, AttributeError) : pass sys.exit(retcode)