Index: /pykota/trunk/pykota/tool.py
===================================================================
--- /pykota/trunk/pykota/tool.py (revision 1257)
+++ /pykota/trunk/pykota/tool.py (revision 1271)
@@ -22,4 +22,8 @@
 #
 # $Log$
+# Revision 1.71  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.70  2004/01/08 14:10:32  jalet
 # Copyright year changed.
@@ -775,34 +779,43 @@
                     self.logger.log_message(_("User %s doesn't have quota on printer %s in the PyKota system, applying external policy (%s) for printer %s") % (self.username, self.printername, commandline, self.printername), "info")
                 if os.system(commandline) :
-                    # if an error occured, we die without error,
-                    # so that the job doesn't stop the print queue.
                     self.logger.log_message(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, self.printername), "error")
                     policy = "EXTERNALERROR"
                     break
             else :        
+                if not printer.Exists :
+                    self.logger.log_message(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.printername, policy), "info")
+                if not user.Exists :
+                    self.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (self.username, policy, self.printername), "info")
+                if not userpquota.Exists :
+                    self.logger.log_message(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.username, self.printername, policy), "info")
                 break
+        if policy == "EXTERNAL" :    
+            if not printer.Exists :
+                self.logger.log_message(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.printername, "info")
+            if not user.Exists :
+                self.logger.log_message(_("User %s still not registered in the PyKota system, job will be rejected on printer %s") % (self.username, self.printername), "info")
+            if not userpquota.Exists :
+                self.logger.log_message(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.username, self.printername), "info")
         return (policy, printer, user, userpquota)
         
-    def main(self) :    
+    def mainWork(self) :    
         """Main work is done here."""
         (policy, printer, user, userpquota) = self.getPrinterUserAndUserPQuota()
+        # TODO : check for last user's quota in case pykota filter is used with querying
         if policy == "EXTERNALERROR" :
             # Policy was 'EXTERNAL' and the external command returned an error code
-            pass # TODO : reject job
+            return self.removeJob()
         elif policy == "EXTERNAL" :
             # Policy was 'EXTERNAL' and the external command wasn't able
             # to add either the printer, user or user print quota
-            pass # TODO : reject job
-        elif policy == "ALLOW":
-            # Either printer, user or user print quota doesn't exist,
-            # but the job should be allowed anyway.
-            pass # TODO : accept job
-        elif policy == "OK" :
-            # Both printer, user and user print quota exist, job should
-            # be allowed if current user is allowed to print on this
-            # printer
-            pass # TODO : decide what to do based on user quota.
-        else : # DENY
+            return self.removeJob()
+        elif policy == "DENY" :    
             # Either printer, user or user print quota doesn't exist,
             # and the job should be rejected.
-            pass # TODO : reject job
+            return self.removeJob()
+        else :
+            if policy not in ("OK", "ALLOW") :
+                self.logger.log_message(_("Invalid policy %s for printer %s") % (policy, self.printername))
+                return self.removeJob()
+            else :
+                return self.doWork(policy, printer, user, userpquota)
Index: /pykota/trunk/pykota/accounters/querying.py
===================================================================
--- /pykota/trunk/pykota/accounters/querying.py (revision 1257)
+++ /pykota/trunk/pykota/accounters/querying.py (revision 1271)
@@ -22,4 +22,8 @@
 #
 # $Log$
+# Revision 1.12  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.11  2004/01/08 14:10:32  jalet
 # Copyright year changed.
@@ -69,4 +73,5 @@
         AccounterBase.__init__(self, kotabackend, arguments)
         self.requester = openRequester(kotabackend.config, kotabackend.printername)
+        self.isDelayed = 1 # With the pykota filter, accounting is delayed by one job
         
     def getPrinterInternalPageCounter(self) :    
@@ -89,10 +94,10 @@
         return counter    
         
-    def beginJob(self, printer, user) :    
+    def beginJob(self, userpquota) :    
         """Saves printer internal page counter at start of job."""
         # save page counter before job
         self.LastPageCounter = self.counterbefore = self.getPrinterInternalPageCounter()
         
-    def endJob(self, printer, user) :    
+    def endJob(self, userpquota) :    
         """Saves printer internal page counter at end of job."""
         # save page counter after job
@@ -122,72 +127,14 @@
         return jobsize
         
-    def doAccounting(self, printer, user) :
+    def doAccounting(self, userpquota) :
         """Does print accounting and returns if the job status is ALLOW or DENY."""
         # Get the page counter directly from the printer itself
-        # Tries MAXTRIES times, sleeping two seconds each time, in case the printer is sleeping.
-        # This was seen with my Apple LaserWriter 16/600 PS which doesn't answer before having warmed up.
-        counterbeforejob = self.getPrinterInternalPageCounter()
+        counterbeforejob = self.getPrinterInternalPageCounter() or 0
         
-        # get last job information for this printer
-        if not printer.LastJob.Exists :
-            # The printer hasn't been used yet, from PyKota's point of view
-            lastuser = user
-            lastpagecounter = counterbeforejob
-        else :    
-            # get last values from Quota Storage
-            lastuser = printer.LastJob.User
-            lastpagecounter = printer.LastJob.PrinterPageCounter
-            
-        # if printer is off then we assume the correct counter value is the last one
-        if counterbeforejob is None :
-            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
-        try :
-            jobsize = (counterbeforejob - lastpagecounter)    
-        except TypeError :    
-            # never used, and internal page counter not accessible
-            jobsize = 0
-            
-        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 the last job's size to 
-            # abs(int((10 - abs(lastcounter(snmp) - lastcounter(storage)) / 2))
-            # 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
-            self.filter.logger.log_message(_("Error in page count value %i for user %s on printer %s") % (jobsize, lastuser.Name, self.filter.printername), "error")
-            jobsize = abs(int((10 - abs(jobsize)) / 2))     # Workaround for HP printers' feature !
-            
-        # update the quota for the previous user on this printer 
-        lastuserquota = self.filter.storage.getUserPQuota(lastuser, printer)
-        if lastuserquota.Exists :
-            lastuserquota.increasePagesUsage(jobsize)
-        
-        # update the last job size in the history
-        if printer.LastJob.Exists :
-            printer.LastJob.setSize(jobsize)
-        
-        # warns the last user if he is over quota
-        if lastuserquota.Exists :
-            self.filter.warnUserPQuota(lastuserquota)
-            
         # Is the current user allowed to print at all ?
-        action = self.filter.warnUserPQuota(self.filter.storage.getUserPQuota(user, printer))
+        action = self.filter.warnUserPQuota(userpquota)
         
         # adds the current job to history    
-        printer.addJobToHistory(self.filter.jobid, user, counterbeforejob, action, filename=self.filter.preserveinputfile, title=self.filter.title, copies=self.filter.copies, options=self.filter.options)
+        userpquota.Printer.addJobToHistory(self.filter.jobid, userpquota.User, counterbeforejob, action, filename=self.filter.preserveinputfile, title=self.filter.title, copies=self.filter.copies, options=self.filter.options)
         return action
             
Index: /pykota/trunk/pykota/accounter.py
===================================================================
--- /pykota/trunk/pykota/accounter.py (revision 1257)
+++ /pykota/trunk/pykota/accounter.py (revision 1271)
@@ -22,4 +22,8 @@
 #
 # $Log$
+# Revision 1.11  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.10  2004/01/08 14:10:32  jalet
 # Copyright year changed.
@@ -69,4 +73,5 @@
         self.filter = kotafilter
         self.arguments = arguments
+        self.isDelayed = 0      # Accounting is immediate by default
         
     def getLastPageCounter(self) :    
@@ -76,24 +81,6 @@
         except :    
             return 0
-        
-    def filterInput(self, inputfile) :
-        """Transparent filter."""
-        mustclose = 0    
-        if inputfile is not None :    
-            if hasattr(inputfile, "read") :
-                infile = inputfile
-            else :    
-                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 beginJob(self, printer, user) :    
+    def beginJob(self, userpquota) :    
         """Saves the computed job size."""
         # computes job's size
@@ -101,5 +88,5 @@
         
         # get last job information for this printer
-        if not printer.LastJob.Exists :
+        if not userpquota.Printer.LastJob.Exists :
             # The printer hasn't been used yet, from PyKota's point of view
             self.LastPageCounter = 0
@@ -108,7 +95,7 @@
             # Last lifetime page counter before actual job is 
             # last page counter + last job size
-            self.LastPageCounter = int(printer.LastJob.PrinterPageCounter or 0) + int(printer.LastJob.JobSize or 0)
+            self.LastPageCounter = int(userpquota.Printer.LastJob.PrinterPageCounter or 0) + int(userpquota.Printer.LastJob.JobSize or 0)
         
-    def endJob(self, printer, user) :    
+    def endJob(self, userpquota) :    
         """Do nothing."""
         pass
@@ -121,16 +108,16 @@
             return 0
         
-    def doAccounting(self, printer, user) :
-        """Deletgates the computation of the job size to an external command.
+    def doAccounting(self, userpquota) :
+        """Delegates the computation of the job size to an external command.
         
            The command must print the job size on its standard output and exit successfully.
         """
-        self.beginJob(printer, user)
+        self.beginJob(userpquota)
         
         # get the job size, which is real job size * number of copies.
+        # TODO : Double check with CUPS documentation : this is not always correct
         jobsize = self.getJobSize() * self.filter.copies
             
         # Is the current user allowed to print at all ?
-        userpquota = self.filter.storage.getUserPQuota(user, printer)
         action = self.filter.warnUserPQuota(userpquota)
         
@@ -142,7 +129,7 @@
         
         # adds the current job to history    
-        jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0)
-        printer.addJobToHistory(self.filter.jobid, user, self.getLastPageCounter(), action, jobsize, jobprice, self.filter.preserveinputfile, self.filter.title, self.filter.copies, self.filter.options)
-        self.endJob(printer, user)
+        jobprice = (float(userpquota.Printer.PricePerPage or 0.0) * jobsize) + float(userpquota.Printer.PricePerJob or 0.0)
+        userpquota.Printer.addJobToHistory(self.filter.jobid, userpquota.User, self.getLastPageCounter(), action, jobsize, jobprice, self.filter.preserveinputfile, self.filter.title, self.filter.copies, self.filter.options)
+        self.endJob(userpquota)
         return action
         
Index: /pykota/trunk/pykota/version.py
===================================================================
--- /pykota/trunk/pykota/version.py (revision 1269)
+++ /pykota/trunk/pykota/version.py (revision 1271)
@@ -22,5 +22,5 @@
 #
 
-__version__ = "1.16alpha23_unofficial"
+__version__ = "1.16alpha24_unofficial"
 
 __doc__ = """PyKota : a complete Printing Quota Solution for CUPS and LPRng."""
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)
Index: /pykota/trunk/bin/pykota
===================================================================
--- /pykota/trunk/bin/pykota (revision 1257)
+++ /pykota/trunk/bin/pykota (revision 1271)
@@ -24,4 +24,8 @@
 #
 # $Log$
+# 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.
@@ -228,74 +232,72 @@
             # 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" :
+            self.logdebug("Does accounting for user %s on printer %s." % (user.Name, printer.Name))
+            action = self.accounter.doAccounting(userpquota)
+            if action == "DENY" :
+                # Just die.
+                self.logdebug("Printing is denied.")
+                return self.removeJob()
             
-def main(thefilter) :    
-    """Do it, and do it right !"""
-    #
-    # If this is a CUPS filter, we should act and die like a CUPS filter when needed
-    if thefilter.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 thefilter.removeJob()
-    
-    # Get the last page counter and last username from the Quota Storage backend
-    printer = thefilter.storage.getPrinter(thefilter.printername)
-    if not printer.Exists :
-        # The printer is unknown from the Quota Storage perspective.
-        # we just cancel the job.
-        thefilter.logger.log_message(_("Printer %s not registered in the PyKota system") % thefilter.printername, "error")
-        return thefilter.removeJob()
-    else :    
-        for dummy in range(2) :
-            user = thefilter.storage.getUser(thefilter.username)
-            if user.Exists :
-                break
+        # pass the job's data to the next filter
+        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 :    
-                # 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) = thefilter.config.getPrinterPolicy(thefilter.printername)
-                if policy == "ALLOW" :
-                    action = "POLICY_ALLOW"
-                elif policy == "EXTERNAL" :    
-                    commandline = thefilter.formatCommandLine(args, user, printer)
-                    thefilter.logger.log_message(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (thefilter.username, commandline, thefilter.printername), "info")
-                    if os.system(commandline) :
-                        thefilter.logger.log_message(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, thefilter.printername), "error")
-                        return thefilter.removeJob()
-                    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"
-                thefilter.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thefilter.username, action, thefilter.printername), "warn")
-                if action == "POLICY_DENY" :
-                    return thefilter.removeJob()
-                # when we get there, the printer policy allows the job to pass 
-                break    
+                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()
+        
+    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)
+                    printer.LastJob.setSize(lastjobsize)
+                    self.logdebug("Updating user %s's quota on printer %s" % (lastuserpquota.User.Name, printer.Name))
+                    lastuserpquota.increasePagesUsage(lastjobsize)
+                    self.warnUserPQuota(lastuserpquota)
                     
-        # if user exists, do accounting
-        if user.Exists :
-            # Now does the accounting and act depending on the result
-            thefilter.logdebug("Does accounting for user %s on printer %s." % (user.Name, printer.Name))
-            action = thefilter.accounter.doAccounting(printer, user)
-        
-            # if not allowed to print then die, else proceed.
-            if action == "DENY" :
-                # No, just die cleanly
-                thefilter.logdebug("Printing is denied.")
-                return thefilter.removeJob()
-        elif policy == "EXTERNAL" :                
-            thefilter.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thefilter.printername, thefilter.username), "error")
-            return thefilter.removeJob()
-        
-        # pass the job untouched to the underlying layer
-        thefilter.logdebug("Passing input data to next filter.")
-        thefilter.accounter.filterInput(thefilter.inputfile)      
-    
-        return thefilter.acceptJob()
-
+        # then deal with current print job as usual            
+        PyKotaFilterOrBackend.mainWork(self)        
+            
 if __name__ == "__main__" :    
     retcode = -1
@@ -303,5 +305,5 @@
         # Initializes the current tool
         kotafilter = PyKotaFilter()    
-        retcode = main(kotafilter)
+        retcode = kotafilter.mainWork()
     except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, PyKotaRequesterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg :
         sys.stderr.write("ERROR : PyKota filter failed (%s)\n" % msg)
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 1270)
+++ /pykota/trunk/NEWS (revision 1271)
@@ -22,4 +22,9 @@
 PyKota NEWS :
 
+    - 1.16alpha24 :
+    
+        - Major code refactoring => It is now possible to
+          automatically add printers on first print.
+        
     - 1.16alpha23 :
     
