Index: pykota/trunk/bin/cupspykota
===================================================================
--- pykota/trunk/bin/cupspykota (revision 1257)
+++ pykota/trunk/bin/cupspykota (revision 1271)
@@ -24,4 +24,8 @@
 #
 # $Log$
+# Revision 1.20  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.19  2004/01/08 14:10:32  jalet
 # Copyright year changed.
@@ -108,4 +112,12 @@
 from pykota.requester import PyKotaRequesterError
 
+gotSigTerm = 0
+def sigterm_handler(signum, frame) :
+    """Sets a global variable whenever SIGTERM is received."""
+    # SIGTERM will be ignore most of the time, but during
+    # the call to the real backend, we have to pass it through.
+    global gotSigTerm
+    gotSigTerm = 1
+    
 class PyKotaPopen3(popen2.Popen3) :
     """Our own class to execute real backends.
@@ -129,103 +141,74 @@
             os._exit(1)
     
-gotSigTerm = 0
-def sigterm_handler(signum, frame) :
-    """Sets a global variable whenever SIGTERM is received."""
-    # SIGTERM will be ignore most of the time, but during
-    # the call to the real backend, we have to pass it through.
-    global gotSigTerm
-    gotSigTerm = 1
-    
-def main(thebackend) :    
-    """Do it, and do it right !"""
-    # first deal with signals
-    # CUPS backends ignore SIGPIPE and exit(1) on SIGTERM
-    # Fortunately SIGPIPE is already ignored by Python
-    # It's there just in case this changes in the future.
-    # Here we have to handle SIGTERM correctly, and pass
-    # it to the original backend if needed.
-    global gotSigTerm
-    gotSigTerm = 0
-    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
-    signal.signal(signal.SIGTERM, sigterm_handler)
-    
-    #
-    # retrieve some informations on the current printer and user
-    # from the Quota Storage.
-    printer = thebackend.storage.getPrinter(thebackend.printername)
-    if not printer.Exists :
-        # The printer is unknown from the Quota Storage perspective
-        # we send an error back to CUPS, which will stop
-        # the print queue.
-        thebackend.logger.log_message(_("Printer %s not registered in the PyKota system") % thebackend.printername, "error")
-        return 1
-    else :    
-        for dummy in range(2) :
-            user = thebackend.storage.getUser(thebackend.username)
-            if user.Exists :
-                break
-            else :    
-                # The user is unknown from the Quota Storage perspective
-                # Depending on the default policy for this printer, we
-                # either let the job pass through or reject it, but we
-                # log a message in any case.
-                (policy, args) = thebackend.config.getPrinterPolicy(thebackend.printername)
-                if policy == "ALLOW" :
-                    action = "POLICY_ALLOW"
-                elif policy == "EXTERNAL" :    
-                    commandline = thebackend.formatCommandLine(args, user, printer)
-                    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")
-                    if os.system(commandline) :
-                        # if an error occured, we die without error,
-                        # so that the job doesn't stop the print queue.
-                        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")
-                        return 0
-                    else :    
-                        # here we try a second time, because the goal
-                        # of the external action was to add the user
-                        # in the database.
-                        continue 
-                else :    
-                    action = "POLICY_DENY"
-                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")
-                if action == "POLICY_DENY" :
-                    # if not allowed to print then die, else proceed.
-                    # we die without error, so that the job doesn't 
-                    # stop the print queue.
-                    return 0
-                # when we get there, the printer policy allows the job to pass 
-                break    
+class PyKotaBackend(PyKotaFilterOrBackend) :        
+    """A class for the pykota backend."""
+    def acceptJob(self) :        
+        """Returns the appropriate exit code to tell CUPS all is OK."""
+        return 0
+            
+    def removeJob(self) :            
+        """Returns the appropriate exit code to let CUPS think all is OK.
+        
+           Returning 0 (success) prevents CUPS from stopping the print queue.
+        """   
+        return 0
+        
+    def doWork(self, policy, printer, user, userpquota) :    
+        """Most of the work is done here."""
+        global gotSigTerm
+        # 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" :
+            self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
+            action = self.warnUserPQuota(userpquota)
+            if action not in ["ALLOW", "WARN"] :    
+                return self.removeJob()
+            self.logdebug("Job accounting begins.")
+            self.accounter.beginJob(userpquota)
+            
+        # pass the job's data to the real backend    
+        if gotSigTerm :
+            retcode = self.removeJob()
+        else :    
+            retcode = self.handleData()        
+        
+        if policy == "OK" :        
+            # stops accounting. 
+            self.accounter.endJob(userpquota)
+            self.logdebug("Job accounting ends.")
+                
+            # retrieve the job size    
+            jobsize = self.accounter.getJobSize()
+            self.logdebug("Job size : %i" % jobsize)
+            
+            # update the quota for the current user on this printer 
+            jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0)
+            if jobsize :
+                self.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name))
+                userpquota.increasePagesUsage(jobsize)
+            
+            # adds the current job to history    
+            printer.addJobToHistory(self.jobid, user, self.accounter.getLastPageCounter(), action, jobsize, jobprice, self.preserveinputfile, self.title, self.copies, self.options)
+            self.logdebug("Job added to history.")
+            
+        return retcode    
                     
-        if user.Exists :
-            # Is the current user allowed to print at all ?
-            thebackend.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name))
-            action = thebackend.warnUserPQuota(thebackend.storage.getUserPQuota(user, printer))
-        elif policy == "EXTERNAL" :                
-            # if the extenal policy produced no error, but the 
-            # user still doesn't exist, we die without error,
-            # so that the job doesn't stop the print queue.
-            thebackend.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thebackend.printername, thebackend.username), "error")
-            return 0
-        
-    if action not in ["ALLOW", "WARN"] :    
-        # if not allowed to print then die, else proceed.
-        # we die without error, so that the job doesn't 
-        # stop the print queue.
-        retcode = 0
-    else :
-        # pass the job untouched to the underlying layer
-        # and starts accounting at the same time
-        thebackend.logdebug("Job accounting begins.")
-        thebackend.accounter.beginJob(printer, user)
-        
+    def handleData(self) :                    
+        """Pass the job's data to the real backend."""
         # Now it becomes tricky...
+        # We must pass the unmodified job to the original backend
+        
+        global gotSigTerm
         
         # First ensure that we have a file object as input
         mustclose = 0    
-        if thebackend.inputfile is not None :    
-            if hasattr(thebackend.inputfile, "read") :
-                infile = thebackend.inputfile
+        if self.inputfile is not None :    
+            if hasattr(self.inputfile, "read") :
+                infile = self.inputfile
             else :    
-                infile = open(thebackend.inputfile, "rb")
+                infile = open(self.inputfile, "rb")
             mustclose = 1
         else :    
@@ -233,8 +216,8 @@
             
         # Find the real backend pathname    
-        realbackend = os.path.join(os.path.split(sys.argv[0])[0], thebackend.originalbackend)
+        realbackend = os.path.join(os.path.split(sys.argv[0])[0], self.originalbackend)
         
         # And launch it
-        thebackend.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])])))
+        self.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])])))
         subprocess = PyKotaPopen3([realbackend] + sys.argv[1:], capturestderr=1, bufsize=0, arg0=os.environ["DEVICE_URI"])
         
@@ -264,5 +247,5 @@
         status = -1
         inputclosed = 0
-        thebackend.logdebug("Entering streams polling loop...")
+        self.logdebug("Entering streams polling loop...")
         while status == -1 :
             # First check if original backend is still alive
@@ -280,5 +263,5 @@
             availablefds = pollster.poll()
             for (fd, mask) in availablefds :
-                # thebackend.logdebug("file: %i    mask: %04x" % (fd, mask))
+                # self.logdebug("file: %i    mask: %04x" % (fd, mask))
                 if mask & select.POLLOUT :
                     # We can write
@@ -326,5 +309,5 @@
                         pollster.unregister(fromcfno)        
                         os.close(fromcfno)
-                        thebackend.logdebug("Real backend's stdout ends.")
+                        self.logdebug("Real backend's stdout ends.")
                     elif fd == cerrfno :    
                         # Original CUPS backend has finished 
@@ -337,5 +320,5 @@
                         pollster.unregister(cerrfno)        
                         os.close(cerrfno)
-                        thebackend.logdebug("Real backend's stderr ends.")
+                        self.logdebug("Real backend's stderr ends.")
                         
             if endinput and not inputclosed :            
@@ -351,5 +334,5 @@
                 os.close(tocfno)
                 inputclosed = 1
-                thebackend.logdebug("Input data ends.")
+                self.logdebug("Input data ends.")
                 
         # Input file was a real file, we have to close it.    
@@ -357,5 +340,5 @@
             infile.close()
             
-        thebackend.logdebug("Exiting streams polling loop...")
+        self.logdebug("Exiting streams polling loop...")
             
         # Check exit code of original CUPS backend.    
@@ -363,31 +346,22 @@
             retcode = os.WEXITSTATUS(status)
         else :    
-            thebackend.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error")
+            self.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error")
             retcode = -1
-    
-    # stops accounting. 
-    thebackend.accounter.endJob(printer, user)
-    thebackend.logdebug("Job accounting ends.")
-        
-    # retrieve the job size    
-    jobsize = thebackend.accounter.getJobSize()
-    thebackend.logdebug("Job size : %i" % jobsize)
-    
-    # update the quota for the current user on this printer 
-    jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0)
-    if jobsize :
-        userquota = thebackend.storage.getUserPQuota(user, printer)
-        if userquota.Exists :
-            thebackend.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name))
-            userquota.increasePagesUsage(jobsize)
-    
-    # adds the current job to history    
-    printer.addJobToHistory(thebackend.jobid, user, thebackend.accounter.getLastPageCounter(), action, jobsize, jobprice, thebackend.preserveinputfile, thebackend.title, thebackend.copies, thebackend.options)
-    thebackend.logdebug("Job added to history.")
-    
-    return retcode # return (retcode or gotSigTerm) shouldn't be needed
-
+        return retcode    
+    
 if __name__ == "__main__" :    
     # This is a CUPS backend, we should act and die like a CUPS backend
+    
+    # first deal with signals
+    # CUPS backends ignore SIGPIPE and exit(1) on SIGTERM
+    # Fortunately SIGPIPE is already ignored by Python
+    # It's there just in case this changes in the future.
+    # Here we have to handle SIGTERM correctly, and pass
+    # it to the original backend if needed.
+    global gotSigTerm
+    gotSigTerm = 0
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+    signal.signal(signal.SIGTERM, sigterm_handler)
+    
     if len(sys.argv) == 1 :
         # we will execute each existing backend in device enumeration mode
@@ -433,6 +407,6 @@
         try :
             # Initializes the backend
-            kotabackend = PyKotaFilterOrBackend()    
-            retcode = main(kotabackend)
+            kotabackend = PyKotaBackend()    
+            retcode = kotabackend.mainWork()
         except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, PyKotaRequesterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
             sys.stderr.write("ERROR : cupspykota backend failed (%s)\n" % msg)
