Show
Ignore:
Timestamp:
01/12/04 00:22:42 (20 years ago)
Author:
jalet
Message:

Major code refactoring, it's way cleaner, and now allows automated addition
of printers on first print.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • pykota/trunk/bin/cupspykota

    r1257 r1271  
    2424# 
    2525# $Log$ 
     26# Revision 1.20  2004/01/11 23:22:42  jalet 
     27# Major code refactoring, it's way cleaner, and now allows automated addition 
     28# of printers on first print. 
     29# 
    2630# Revision 1.19  2004/01/08 14:10:32  jalet 
    2731# Copyright year changed. 
     
    108112from pykota.requester import PyKotaRequesterError 
    109113 
     114gotSigTerm = 0 
     115def sigterm_handler(signum, frame) : 
     116    """Sets a global variable whenever SIGTERM is received.""" 
     117    # SIGTERM will be ignore most of the time, but during 
     118    # the call to the real backend, we have to pass it through. 
     119    global gotSigTerm 
     120    gotSigTerm = 1 
     121     
    110122class PyKotaPopen3(popen2.Popen3) : 
    111123    """Our own class to execute real backends. 
     
    129141            os._exit(1) 
    130142     
    131 gotSigTerm = 0 
    132 def sigterm_handler(signum, frame) : 
    133     """Sets a global variable whenever SIGTERM is received.""" 
    134     # SIGTERM will be ignore most of the time, but during 
    135     # the call to the real backend, we have to pass it through. 
    136     global gotSigTerm 
    137     gotSigTerm = 1 
    138      
    139 def main(thebackend) :     
    140     """Do it, and do it right !""" 
    141     # first deal with signals 
    142     # CUPS backends ignore SIGPIPE and exit(1) on SIGTERM 
    143     # Fortunately SIGPIPE is already ignored by Python 
    144     # It's there just in case this changes in the future. 
    145     # Here we have to handle SIGTERM correctly, and pass 
    146     # it to the original backend if needed. 
    147     global gotSigTerm 
    148     gotSigTerm = 0 
    149     signal.signal(signal.SIGPIPE, signal.SIG_IGN) 
    150     signal.signal(signal.SIGTERM, sigterm_handler) 
    151      
    152     # 
    153     # retrieve some informations on the current printer and user 
    154     # from the Quota Storage. 
    155     printer = thebackend.storage.getPrinter(thebackend.printername) 
    156     if not printer.Exists : 
    157         # The printer is unknown from the Quota Storage perspective 
    158         # we send an error back to CUPS, which will stop 
    159         # the print queue. 
    160         thebackend.logger.log_message(_("Printer %s not registered in the PyKota system") % thebackend.printername, "error") 
    161         return 1 
    162     else :     
    163         for dummy in range(2) : 
    164             user = thebackend.storage.getUser(thebackend.username) 
    165             if user.Exists : 
    166                 break 
    167             else :     
    168                 # The user is unknown from the Quota Storage perspective 
    169                 # Depending on the default policy for this printer, we 
    170                 # either let the job pass through or reject it, but we 
    171                 # log a message in any case. 
    172                 (policy, args) = thebackend.config.getPrinterPolicy(thebackend.printername) 
    173                 if policy == "ALLOW" : 
    174                     action = "POLICY_ALLOW" 
    175                 elif policy == "EXTERNAL" :     
    176                     commandline = thebackend.formatCommandLine(args, user, printer) 
    177                     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") 
    178                     if os.system(commandline) : 
    179                         # if an error occured, we die without error, 
    180                         # so that the job doesn't stop the print queue. 
    181                         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") 
    182                         return 0 
    183                     else :     
    184                         # here we try a second time, because the goal 
    185                         # of the external action was to add the user 
    186                         # in the database. 
    187                         continue  
    188                 else :     
    189                     action = "POLICY_DENY" 
    190                 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") 
    191                 if action == "POLICY_DENY" : 
    192                     # if not allowed to print then die, else proceed. 
    193                     # we die without error, so that the job doesn't  
    194                     # stop the print queue. 
    195                     return 0 
    196                 # when we get there, the printer policy allows the job to pass  
    197                 break     
     143class PyKotaBackend(PyKotaFilterOrBackend) :         
     144    """A class for the pykota backend.""" 
     145    def acceptJob(self) :         
     146        """Returns the appropriate exit code to tell CUPS all is OK.""" 
     147        return 0 
     148             
     149    def removeJob(self) :             
     150        """Returns the appropriate exit code to let CUPS think all is OK. 
     151         
     152           Returning 0 (success) prevents CUPS from stopping the print queue. 
     153        """    
     154        return 0 
     155         
     156    def doWork(self, policy, printer, user, userpquota) :     
     157        """Most of the work is done here.""" 
     158        global gotSigTerm 
     159        # Two different values possible for policy here : 
     160        # ALLOW means : Either printer, user or user print quota doesn't exist, 
     161        #               but the job should be allowed anyway. 
     162        # OK means : Both printer, user and user print quota exist, job should 
     163        #            be allowed if current user is allowed to print on this printer 
     164        if policy == "OK" : 
     165            self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name)) 
     166            action = self.warnUserPQuota(userpquota) 
     167            if action not in ["ALLOW", "WARN"] :     
     168                return self.removeJob() 
     169            self.logdebug("Job accounting begins.") 
     170            self.accounter.beginJob(userpquota) 
     171             
     172        # pass the job's data to the real backend     
     173        if gotSigTerm : 
     174            retcode = self.removeJob() 
     175        else :     
     176            retcode = self.handleData()         
     177         
     178        if policy == "OK" :         
     179            # stops accounting.  
     180            self.accounter.endJob(userpquota) 
     181            self.logdebug("Job accounting ends.") 
     182                 
     183            # retrieve the job size     
     184            jobsize = self.accounter.getJobSize() 
     185            self.logdebug("Job size : %i" % jobsize) 
     186             
     187            # update the quota for the current user on this printer  
     188            jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0) 
     189            if jobsize : 
     190                self.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name)) 
     191                userpquota.increasePagesUsage(jobsize) 
     192             
     193            # adds the current job to history     
     194            printer.addJobToHistory(self.jobid, user, self.accounter.getLastPageCounter(), action, jobsize, jobprice, self.preserveinputfile, self.title, self.copies, self.options) 
     195            self.logdebug("Job added to history.") 
     196             
     197        return retcode     
    198198                     
    199         if user.Exists : 
    200             # Is the current user allowed to print at all ? 
    201             thebackend.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name)) 
    202             action = thebackend.warnUserPQuota(thebackend.storage.getUserPQuota(user, printer)) 
    203         elif policy == "EXTERNAL" :                 
    204             # if the extenal policy produced no error, but the  
    205             # user still doesn't exist, we die without error, 
    206             # so that the job doesn't stop the print queue. 
    207             thebackend.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thebackend.printername, thebackend.username), "error") 
    208             return 0 
    209          
    210     if action not in ["ALLOW", "WARN"] :     
    211         # if not allowed to print then die, else proceed. 
    212         # we die without error, so that the job doesn't  
    213         # stop the print queue. 
    214         retcode = 0 
    215     else : 
    216         # pass the job untouched to the underlying layer 
    217         # and starts accounting at the same time 
    218         thebackend.logdebug("Job accounting begins.") 
    219         thebackend.accounter.beginJob(printer, user) 
    220          
     199    def handleData(self) :                     
     200        """Pass the job's data to the real backend.""" 
    221201        # Now it becomes tricky... 
     202        # We must pass the unmodified job to the original backend 
     203         
     204        global gotSigTerm 
    222205         
    223206        # First ensure that we have a file object as input 
    224207        mustclose = 0     
    225         if thebackend.inputfile is not None :     
    226             if hasattr(thebackend.inputfile, "read") : 
    227                 infile = thebackend.inputfile 
     208        if self.inputfile is not None :     
     209            if hasattr(self.inputfile, "read") : 
     210                infile = self.inputfile 
    228211            else :     
    229                 infile = open(thebackend.inputfile, "rb") 
     212                infile = open(self.inputfile, "rb") 
    230213            mustclose = 1 
    231214        else :     
     
    233216             
    234217        # Find the real backend pathname     
    235         realbackend = os.path.join(os.path.split(sys.argv[0])[0], thebackend.originalbackend) 
     218        realbackend = os.path.join(os.path.split(sys.argv[0])[0], self.originalbackend) 
    236219         
    237220        # And launch it 
    238         thebackend.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])]))) 
     221        self.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])]))) 
    239222        subprocess = PyKotaPopen3([realbackend] + sys.argv[1:], capturestderr=1, bufsize=0, arg0=os.environ["DEVICE_URI"]) 
    240223         
     
    264247        status = -1 
    265248        inputclosed = 0 
    266         thebackend.logdebug("Entering streams polling loop...") 
     249        self.logdebug("Entering streams polling loop...") 
    267250        while status == -1 : 
    268251            # First check if original backend is still alive 
     
    280263            availablefds = pollster.poll() 
    281264            for (fd, mask) in availablefds : 
    282                 # thebackend.logdebug("file: %i    mask: %04x" % (fd, mask)) 
     265                # self.logdebug("file: %i    mask: %04x" % (fd, mask)) 
    283266                if mask & select.POLLOUT : 
    284267                    # We can write 
     
    326309                        pollster.unregister(fromcfno)         
    327310                        os.close(fromcfno) 
    328                         thebackend.logdebug("Real backend's stdout ends.") 
     311                        self.logdebug("Real backend's stdout ends.") 
    329312                    elif fd == cerrfno :     
    330313                        # Original CUPS backend has finished  
     
    337320                        pollster.unregister(cerrfno)         
    338321                        os.close(cerrfno) 
    339                         thebackend.logdebug("Real backend's stderr ends.") 
     322                        self.logdebug("Real backend's stderr ends.") 
    340323                         
    341324            if endinput and not inputclosed :             
     
    351334                os.close(tocfno) 
    352335                inputclosed = 1 
    353                 thebackend.logdebug("Input data ends.") 
     336                self.logdebug("Input data ends.") 
    354337                 
    355338        # Input file was a real file, we have to close it.     
     
    357340            infile.close() 
    358341             
    359         thebackend.logdebug("Exiting streams polling loop...") 
     342        self.logdebug("Exiting streams polling loop...") 
    360343             
    361344        # Check exit code of original CUPS backend.     
     
    363346            retcode = os.WEXITSTATUS(status) 
    364347        else :     
    365             thebackend.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error") 
     348            self.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error") 
    366349            retcode = -1 
    367      
    368     # stops accounting.  
    369     thebackend.accounter.endJob(printer, user) 
    370     thebackend.logdebug("Job accounting ends.") 
    371          
    372     # retrieve the job size     
    373     jobsize = thebackend.accounter.getJobSize() 
    374     thebackend.logdebug("Job size : %i" % jobsize) 
    375      
    376     # update the quota for the current user on this printer  
    377     jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0) 
    378     if jobsize : 
    379         userquota = thebackend.storage.getUserPQuota(user, printer) 
    380         if userquota.Exists : 
    381             thebackend.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name)) 
    382             userquota.increasePagesUsage(jobsize) 
    383      
    384     # adds the current job to history     
    385     printer.addJobToHistory(thebackend.jobid, user, thebackend.accounter.getLastPageCounter(), action, jobsize, jobprice, thebackend.preserveinputfile, thebackend.title, thebackend.copies, thebackend.options) 
    386     thebackend.logdebug("Job added to history.") 
    387      
    388     return retcode # return (retcode or gotSigTerm) shouldn't be needed 
    389  
     350        return retcode     
     351     
    390352if __name__ == "__main__" :     
    391353    # This is a CUPS backend, we should act and die like a CUPS backend 
     354     
     355    # first deal with signals 
     356    # CUPS backends ignore SIGPIPE and exit(1) on SIGTERM 
     357    # Fortunately SIGPIPE is already ignored by Python 
     358    # It's there just in case this changes in the future. 
     359    # Here we have to handle SIGTERM correctly, and pass 
     360    # it to the original backend if needed. 
     361    global gotSigTerm 
     362    gotSigTerm = 0 
     363    signal.signal(signal.SIGPIPE, signal.SIG_IGN) 
     364    signal.signal(signal.SIGTERM, sigterm_handler) 
     365     
    392366    if len(sys.argv) == 1 : 
    393367        # we will execute each existing backend in device enumeration mode 
     
    433407        try : 
    434408            # Initializes the backend 
    435             kotabackend = PyKotaFilterOrBackend()     
    436             retcode = main(kotabackend) 
     409            kotabackend = PyKotaBackend()     
     410            retcode = kotabackend.mainWork() 
    437411        except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, PyKotaRequesterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg : 
    438412            sys.stderr.write("ERROR : cupspykota backend failed (%s)\n" % msg)