Changeset 973

Show
Ignore:
Timestamp:
04/29/03 20:37:54 (22 years ago)
Author:
jalet
Message:

Pluggable accounting methods (actually doesn't support external scripts)

Location:
pykota/trunk
Files:
4 added
11 modified

Legend:

Unmodified
Added
Removed
  • pykota/trunk/bin/pykota

    r966 r973  
    2323# 
    2424# $Log$ 
     25# Revision 1.29  2003/04/29 18:37:54  jalet 
     26# Pluggable accounting methods (actually doesn't support external scripts) 
     27# 
    2528# Revision 1.28  2003/04/26 08:41:24  jalet 
    2629# Small code reorganisation (UNTESTED) to allow pluggable accounting 
     
    133136 
    134137from pykota.tool import PyKotaTool, PyKotaToolError 
    135 from pykota.requester import openRequester, PyKotaRequesterError 
    136  
    137 MAXTRIES = 6     # maximum number of tries to get the printer's internal page counter 
    138 TIMETOSLEEP = 10 # number of seconds to sleep between two tries to get the printer's internal page counter 
     138from pykota.storage import PyKotaStorageError 
     139from pykota.accounter import openAccounter, PyKotaAccounterError 
    139140 
    140141class PyKotaFilter(PyKotaTool) :     
     
    143144        PyKotaTool.__init__(self) 
    144145        (self.printingsystem, self.printerhostname, self.printername, self.username, self.jobid, self.inputfile) = self.extractInfoFromCupsOrLprng() 
    145         self.requester = openRequester(self.config, self.printername) 
    146      
     146        self.accounter = openAccounter(self) 
     147     
     148    def extractInfoFromCupsOrLprng(self) :     
     149        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename) depending on the printing system in use (as seen by the print filter). 
     150         
     151           Returns (None, None, None, None, None, None) if no printing system is recognized. 
     152        """ 
     153        # Try to detect CUPS 
     154        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) : 
     155            if len(sys.argv) == 7 : 
     156                inputfile = sys.argv[6] 
     157            else :     
     158                inputfile = None 
     159                 
     160            device_uri = os.environ.get("DEVICE_URI", "") 
     161            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp 
     162            try : 
     163                (backend, destination) = device_uri.split(":", 1)  
     164            except ValueError :     
     165                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri 
     166            while destination.startswith("/") : 
     167                destination = destination[1:] 
     168            printerhostname = destination.split("/")[0].split(":")[0] 
     169            return ("CUPS", printerhostname, os.environ.get("PRINTER"), sys.argv[2].strip(), sys.argv[1].strip(), inputfile) 
     170        else :     
     171            # Try to detect LPRng 
     172            jseen = Jseen = Pseen = nseen = rseen = None 
     173            for arg in sys.argv : 
     174                if arg.startswith("-j") : 
     175                    jseen = arg[2:].strip() 
     176                elif arg.startswith("-J") :     
     177                    Jseen = arg[2:].strip() 
     178                    if Jseen == "(STDIN)" : 
     179                        Jseen = None 
     180                elif arg.startswith("-n") :      
     181                    nseen = arg[2:].strip() 
     182                elif arg.startswith("-P") :     
     183                    Pseen = arg[2:].strip() 
     184                elif arg.startswith("-r") :     
     185                    rseen = arg[2:].strip() 
     186            if jseen and Pseen and nseen and rseen :         
     187                return ("LPRNG", rseen, Pseen, nseen, jseen, Jseen) 
     188        return (None, None, None, None, None, None)   # Unknown printing system           
     189         
    147190    def filterInput(self, inputfile) : 
    148191        """Transparent filter.""" 
     
    182225def main() :     
    183226    """Do it, and do it right !""" 
    184     global MAXTRIES, TIMETOSLEEP 
    185      
    186227    # Initializes the current tool 
    187228    kotafilter = PyKotaFilter()     
     
    216257                return kotafilter.removeJob() 
    217258        else : 
    218             # Get the page counter directly from the printer itself 
    219             # Tries MAXTRIES times, sleeping two seconds each time, in case the printer is sleeping. 
    220             # This was seen with my Apple LaserWriter 16/600 PS which doesn't answer before having warmed up. 
    221             for i in range(MAXTRIES) : 
    222                 try : 
    223                     counterbeforejob = kotafilter.requester.getPrinterPageCounter(kotafilter.printerhostname) 
    224                 except PyKotaRequesterError, msg : 
    225                     # can't get actual page counter, assume printer is off or warming up 
    226                     # log the message anyway. 
    227                     kotafilter.logger.log_message("%s" % msg, "warn") 
    228                     counterbeforejob = None 
    229                     printerIsOff = 1 
    230                 else :     
    231                     # printer answered, it is on so we can exit the loop 
    232                     printerIsOff = 0 
    233                     break 
    234                 time.sleep(TIMETOSLEEP)     
    235          
    236             # get last job information for this printer 
    237             pgc = kotafilter.storage.getPrinterPageCounter(printerid)     
    238             if pgc is None : 
    239                 # The printer hasn't been used yet, from PyKota's point of view 
    240                 lasthistoryid = None 
    241                 lastjobid = kotafilter.jobid 
    242                 lastuserid = userid 
    243                 lastusername = kotafilter.username 
    244                 lastpagecounter = counterbeforejob 
    245             else :     
    246                 # get last values from Quota Storage 
    247                 (lasthistoryid, lastjobid, lastuserid, lastusername, lastpagecounter) = (pgc["id"], pgc["jobid"], pgc["userid"], pgc["username"], pgc["pagecounter"]) 
    248                  
    249             # if printer is off then we assume the correct counter value is the last one 
    250             if printerIsOff : 
    251                 counterbeforejob = lastpagecounter 
    252                  
    253             # if the internal lifetime page counter for this printer is 0     
    254             # then this may be a printer with a volatile counter (never 
    255             # saved to NVRAM) which has just been switched off and then on 
    256             # so we use the last page counter from the Quota Storage instead 
    257             # explanation at : http://web.mit.edu/source/third/lprng/doc/LPRng-HOWTO-15.html 
    258             if counterbeforejob == 0 : 
    259                 counterbeforejob = lastpagecounter 
    260                  
    261             # Computes the last job size as the difference between internal page 
    262             # counter in the printer and last page counter taken from the Quota 
    263             # Storage database for this particular printer 
    264             try : 
    265                 jobsize = (counterbeforejob - lastpagecounter)     
    266             except TypeError :     
    267                 # never used, and internal page counter not accessible 
    268                 jobsize = 0 
    269                  
    270             if jobsize < 0 : 
    271                 # Probably an HP printer which was switched off and back on,  
    272                 # its primary counter is only saved in a 10 increment, so 
    273                 # it may be lower than the last page counter saved in the 
    274                 # Quota Storage.  
    275                 # We unconditionnally set the last job's size to  
    276                 # abs(int((10 - abs(lastcounter(snmp) - lastcounter(storage)) / 2)) 
    277                 # For more accurate accounting, don't switch off your HP printers ! 
    278                 # explanation at : http://web.mit.edu/source/third/lprng/doc/LPRng-HOWTO-15.html 
    279                 kotafilter.logger.log_message(_("Error in page count value %i for user %s on printer %s") % (jobsize, lastusername, kotafilter.printername), "error") 
    280                 jobsize = abs(int((10 - abs(jobsize)) / 2))     # Workaround for HP printers' feature ! 
    281                  
    282             # update the quota for the previous user on this printer  
    283             kotafilter.storage.updateUserPQuota(lastuserid, printerid, jobsize) 
    284              
    285             # update the last job size in the history 
    286             kotafilter.storage.updateJobSizeInHistory(lasthistoryid, jobsize) 
    287              
    288             # warns the last user if he is over quota 
    289             kotafilter.warnUserPQuota(lastusername, kotafilter.printername) 
    290                  
    291             # Is the current user allowed to print at all ? 
    292             action = kotafilter.warnUserPQuota(kotafilter.username, kotafilter.printername) 
    293              
    294             # adds the current job to history     
    295             kotafilter.storage.addJobToHistory(kotafilter.jobid, kotafilter.storage.getUserId(kotafilter.username), printerid, counterbeforejob, action) 
     259            # Now does the accounting and act depending on the result 
     260            action = kotafilter.accounter.doAccounting(printerid, userid) 
    296261             
    297262            # if not allowed to print then die, else proceed. 
     
    306271 
    307272if __name__ == "__main__" :     
    308     sys.exit(main()) 
     273    try : 
     274        retcode = main() 
     275    except (PyKotaToolError, PyKotaStorageError, PyKotaAccounterError), msg : 
     276        sys.stderr.write("ERROR : PyKota filter failed (%s)\n" % msg) 
     277        sys.stderr.flush() 
     278        retcode = -1 
     279    sys.exit(retcode)     
     280     
  • pykota/trunk/conf/pykota.conf.sample

    r964 r973  
    8787[lp] 
    8888 
    89 # How to query the lp printer for its page counter 
     89# What is the accounting backend to use 
     90#  
     91# supported values : 
     92# 
     93#    - querying : asks the printer for its lifetime page counter 
     94#    - more to be added in the future 
     95# 
     96accounter: querying 
     97 
     98# How to query the lp printer for its page counter, if needed 
    9099# Only snmp(community, oid) and external(command) are supported 
    91100# 
     
    113122requester: external(snmpget -c public -Ov %(printer)s 43.10.2.1.4.1.1 | cut -f 2,2 -d " ") 
    114123 
     124 
    115125# Default policy for inexistant users (e.g. root) 
    116126# either allow or deny 
  • pykota/trunk/NEWS

    r970 r973  
    2222PyKota NEWS : 
    2323 
     24    - 1.05alpha2 : 
     25     
     26        - Pluggable accounting methods. 
     27         
    2428    - 1.05alpha1 : 
    2529     
  • pykota/trunk/po/en/pykota.po

    r952 r973  
    2121# 
    2222# $Log$ 
     23# Revision 1.22  2003/04/29 18:37:54  jalet 
     24# Pluggable accounting methods (actually doesn't support external scripts) 
     25# 
    2326# Revision 1.21  2003/04/23 22:13:56  jalet 
    2427# Preliminary support for LPRng added BUT STILL UNTESTED. 
     
    325328msgid "Group %s not found in the PyKota Storage." 
    326329msgstr "" 
     330 
     331msgid "Unsupported accounter backend %s" 
     332msgstr "" 
     333 
     334msgid "Option accounter in section %s only supports values in %s" 
     335msgstr "" 
     336 
     337msgid "Option requester for printer %s was not set" 
     338msgstr "" 
  • pykota/trunk/po/fr/pykota.po

    r952 r973  
    2121# 
    2222# $Log$ 
     23# Revision 1.21  2003/04/29 18:37:54  jalet 
     24# Pluggable accounting methods (actually doesn't support external scripts) 
     25# 
    2326# Revision 1.20  2003/04/23 22:13:57  jalet 
    2427# Preliminary support for LPRng added BUT STILL UNTESTED. 
     
    338341msgid "Group %s not found in the PyKota Storage." 
    339342msgstr "Groupe %s non trouv�ans le Quota Storage." 
     343 
     344msgid "Unsupported accounter backend %s" 
     345msgstr "Backend accounter %s non support� 
     346msgid "Option accounter in section %s only supports values in %s" 
     347msgstr "L'option accounter pour l'imprimante %s supporte seulement les valeurs %s" 
     348 
     349msgid "Option requester for printer %s was not set" 
     350msgstr "L'option requester pour l'imprimante %s n'a pas � d�nie" 
  • pykota/trunk/po/pykota.pot

    r952 r973  
    2121# 
    2222# $Log$ 
     23# Revision 1.22  2003/04/29 18:37:54  jalet 
     24# Pluggable accounting methods (actually doesn't support external scripts) 
     25# 
    2326# Revision 1.21  2003/04/23 22:13:56  jalet 
    2427# Preliminary support for LPRng added BUT STILL UNTESTED. 
     
    325328msgid "Group %s not found in the PyKota Storage." 
    326329msgstr "" 
     330 
     331msgid "Unsupported accounter backend %s" 
     332msgstr "" 
     333 
     334msgid "Option accounter in section %s only supports values in %s" 
     335msgstr "" 
     336 
     337msgid "Option requester for printer %s was not set" 
     338msgstr "" 
  • pykota/trunk/pykota/config.py

    r956 r973  
    2121# 
    2222# $Log$ 
     23# Revision 1.24  2003/04/29 18:37:54  jalet 
     24# Pluggable accounting methods (actually doesn't support external scripts) 
     25# 
    2326# Revision 1.23  2003/04/24 11:53:48  jalet 
    2427# Default policy for unknown users/groups is to DENY printing instead 
     
    139142        self.config = ConfigParser.ConfigParser() 
    140143        self.config.read([self.filename]) 
    141         self.checkConfiguration() 
    142          
    143     def checkConfiguration(self) : 
    144         """Checks if configuration is correct. 
    145          
    146            raises PyKotaConfigError in case a problem is detected 
    147         """ 
    148         validmethods = [ "lazy" ] # TODO add more methods             
    149         if self.config.get("global", "method", raw=1).lower() not in validmethods :              
    150             raise PyKotaConfigError, _("Option method only supports values in %s") % str(validmethods) 
    151144                         
    152145    def getPrinterNames(self) :     
     
    198191        return logger     
    199192         
     193    def getAccounterBackend(self, printer) :     
     194        """Returns the accounter backend to use for a given printer. 
     195         
     196           if it is not set, it defaults to 'querying' which means ask printer 
     197           for its internal lifetime page counter. 
     198        """    
     199        validaccounters = [ "querying" ]      
     200        try : 
     201            accounter = self.getPrinterOption(printer, "accounter").lower() 
     202        except PyKotaConfigError :     
     203            accounter = "querying" 
     204        if accounter not in validaccounters : 
     205            raise PyKotaConfigError, _("Option accounter in section %s only supports values in %s") % (printer, str(validaccounters)) 
     206        return accounter 
     207         
    200208    def getRequesterBackend(self, printer) :     
    201209        """Returns the requester backend to use for a given printer, with its arguments.""" 
    202         fullrequester = self.getPrinterOption(printer, "requester") 
    203         try : 
    204             (requester, args) = [x.strip() for x in fullrequester.split('(', 1)] 
    205         except ValueError :     
    206             raise PyKotaConfigError, _("Invalid requester %s for printer %s") % (fullrequester, printer) 
    207         if args.endswith(')') : 
    208             args = args[:-1] 
    209         if not args : 
    210             raise PyKotaConfigError, _("Invalid requester %s for printer %s") % (fullrequester, printer) 
    211         validrequesters = [ "snmp", "external" ] # TODO : add more requesters 
    212         if requester not in validrequesters : 
    213             raise PyKotaConfigError, _("Option requester for printer %s only supports values in %s") % (printer, str(validrequesters)) 
    214         return (requester, args) 
     210        try : 
     211            fullrequester = self.getPrinterOption(printer, "requester") 
     212        except PyKotaConfigError :     
     213            # No requester defined, maybe it is not needed if accounting method 
     214            # is not set to 'querying', but if we are called, then the accounting 
     215            # method really IS 'querying', and so there's a big problem. 
     216            raise PyKotaConfigError, _("Option requester for printer %s was not set") % printer 
     217        else :     
     218            try : 
     219                (requester, args) = [x.strip() for x in fullrequester.split('(', 1)] 
     220            except ValueError :     
     221                raise PyKotaConfigError, _("Invalid requester %s for printer %s") % (fullrequester, printer) 
     222            if args.endswith(')') : 
     223                args = args[:-1] 
     224            if not args : 
     225                raise PyKotaConfigError, _("Invalid requester %s for printer %s") % (fullrequester, printer) 
     226            validrequesters = [ "snmp", "external" ] # TODO : add more requesters 
     227            if requester not in validrequesters : 
     228                raise PyKotaConfigError, _("Option requester for printer %s only supports values in %s") % (printer, str(validrequesters)) 
     229            return (requester, args) 
    215230         
    216231    def getPrinterPolicy(self, printer) :     
  • pykota/trunk/pykota/tool.py

    r956 r973  
    2121# 
    2222# $Log$ 
     23# Revision 1.39  2003/04/29 18:37:54  jalet 
     24# Pluggable accounting methods (actually doesn't support external scripts) 
     25# 
    2326# Revision 1.38  2003/04/24 11:53:48  jalet 
    2427# Default policy for unknown users/groups is to DENY printing instead 
     
    200203        self.storage = storage.openConnection(self.config, asadmin=asadmin) 
    201204        self.smtpserver = self.config.getSMTPServer() 
    202          
    203     def extractInfoFromCupsOrLprng(self) :     
    204         """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename) depending on the printing system in use (as seen by the print filter). 
    205          
    206            Returns (None, None, None, None, None, None) if no printing system is recognized. 
    207         """ 
    208         # Try to detect CUPS 
    209         if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) : 
    210             if len(sys.argv) == 7 : 
    211                 inputfile = sys.argv[6] 
    212             else :     
    213                 inputfile = None 
    214                  
    215             device_uri = os.environ.get("DEVICE_URI", "") 
    216             # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp 
    217             try : 
    218                 (backend, destination) = device_uri.split(":", 1)  
    219             except ValueError :     
    220                 raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri 
    221             while destination.startswith("/") : 
    222                 destination = destination[1:] 
    223             printerhostname = destination.split("/")[0].split(":")[0] 
    224             return ("CUPS", printerhostname, os.environ.get("PRINTER"), sys.argv[2].strip(), sys.argv[1].strip(), inputfile) 
    225         else :     
    226             # Try to detect LPRng 
    227             jseen = Jseen = Pseen = nseen = rseen = None 
    228             for arg in sys.argv : 
    229                 if arg.startswith("-j") : 
    230                     jseen = arg[2:].strip() 
    231                 elif arg.startswith("-J") :     
    232                     Jseen = arg[2:].strip() 
    233                     if Jseen == "(STDIN)" : 
    234                         Jseen = None 
    235                 elif arg.startswith("-n") :      
    236                     nseen = arg[2:].strip() 
    237                 elif arg.startswith("-P") :     
    238                     Pseen = arg[2:].strip() 
    239                 elif arg.startswith("-r") :     
    240                     rseen = arg[2:].strip() 
    241             if jseen and Pseen and nseen and rseen :         
    242                 return ("LPRNG", rseen, Pseen, nseen, jseen, Jseen) 
    243         return (None, None, None, None, None, None)   # Unknown printing system           
    244205         
    245206    def display_version_and_quit(self) : 
  • pykota/trunk/pykota/version.py

    r962 r973  
    2121# 
    2222 
    23 __version__ = "1.05alpha1-unofficial" 
     23__version__ = "1.05alpha2-unofficial" 
    2424 
    2525__doc__ = """PyKota : a complete Printing Quota Solution for CUPS and LPRng.""" 
  • pykota/trunk/setup.py

    r952 r973  
    2323# 
    2424# $Log$ 
     25# Revision 1.13  2003/04/29 18:37:54  jalet 
     26# Pluggable accounting methods (actually doesn't support external scripts) 
     27# 
    2528# Revision 1.12  2003/04/23 22:13:56  jalet 
    2629# Preliminary support for LPRng added BUT STILL UNTESTED. 
     
    7376import shutil 
    7477from distutils.core import setup 
     78import ConfigParser 
    7579 
    7680sys.path.insert(0, "pykota") 
     
    155159            else :         
    156160                sys.stderr.write("WARNING : PyKota won't run without a configuration file !\n") 
     161    else :             
     162        # Configuration file already exists. Check if this is an old version or not 
     163        # if the 'method: lazy' line is present, then the configuration file 
     164        # has to be updated. 
     165        oldconf = ConfigParser.ConfigParser() 
     166        oldconf.read(["/etc/pykota.conf"]) 
     167        try : 
     168            if oldconf.get("global", "method", raw=1).lower().strip() == "lazy" : 
     169                sys.stdout.write("You have got an OLD PyKota configuration file !\n") 
     170                sys.stdout.write("The 'method' statement IS NOT SUPPORTED ANYMORE\nand was replaced with the 'accounter' statement.\n")  
     171                sys.stdout.write("You have to manually set an 'accounter' statement,\neither globally or for each printer.\n") 
     172                sys.stdout.write("Please read the sample configuration file conf/pykota.conf.sample\n") 
     173                sys.stdout.write("to learn how to MANUALLY apply the modifications needed,\nafter the installation is done.\n") 
     174                sys.stdout.write("If you don't do this, then PyKota will stop working !\n") 
     175                answer = raw_input("Please, press ENTER when you'll have read the above paragraph.") 
     176        except ConfigParser.NoOptionError : 
     177            # New configuration file, OK 
     178            pass 
    157179     
    158180    # checks if some needed Python modules are there or not. 
     
    189211      author_email = "alet@librelogiciel.com", 
    190212      url = "http://www.librelogiciel.com/software/", 
    191       packages = [ "pykota", "pykota.storages", "pykota.requesters", "pykota.loggers" ], 
     213      packages = [ "pykota", "pykota.storages", "pykota.requesters", "pykota.loggers", "pykota.accounters" ], 
    192214      scripts = [ "bin/pykota", "bin/edpykota", "bin/repykota", "bin/warnpykota" ], 
    193215      data_files = data_files)