Index: pykota/trunk/bin/cupspykota
===================================================================
--- pykota/trunk/bin/cupspykota (revision 3411)
+++ pykota/trunk/bin/cupspykota (revision 3413)
@@ -9,10 +9,10 @@
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
@@ -51,16 +51,16 @@
 from pykota.accounter import openAccounter
 from pykota import cups
-    
-class FakeObject :        
+
+class FakeObject :
     """Fake object."""
     def __init__(self, name) :
         """Fake init."""
         self.Name = name
-        
-class FakePrinter(FakeObject) :        
+
+class FakePrinter(FakeObject) :
     """Fake printer instance."""
     pass
-    
-class FakeUser(FakeObject) :    
+
+class FakeUser(FakeObject) :
     """Fake user instance."""
     def __init__(self, name) :
@@ -68,5 +68,5 @@
         self.Email = name
         FakeObject.__init__(self, name)
-    
+
 class CUPSBackend(PyKotaTool) :
     """Base class for tools with no database access."""
@@ -83,6 +83,6 @@
         self.lockfilename = None
         self.lockfile = None
-        
-    def deferredInit(self) :    
+
+    def deferredInit(self) :
         """Deferred initialization."""
         PyKotaTool.deferredInit(self)
@@ -91,10 +91,10 @@
             username = self.effectiveUserName
             raise config.PyKotaConfigError, _("User %(username)s is not allowed to read ~pykota/pykotadmin.conf, you must check the permissions.") % locals()
-        
-    def enableSigInt(self) :    
+
+    def enableSigInt(self) :
         """Enables the SIGINT signal (which raises KeyboardInterrupt)."""
         signal.signal(signal.SIGINT, self.oldSigIntHandler)
-        
-    def waitForLock(self) :    
+
+    def waitForLock(self) :
         """Waits until we can acquire the lock file."""
         self.logdebug("Waiting for lock %s to become available..." % self.lockfilename)
@@ -104,12 +104,12 @@
                 # open the lock file, optionally creating it if needed.
                 self.lockfile = open(self.lockfilename, "a+")
-                
+
                 # we wait indefinitely for the lock to become available.
                 # works over NFS too.
                 fcntl.lockf(self.lockfile, fcntl.LOCK_EX)
                 haslock = True
-                
+
                 self.logdebug("Lock %s acquired." % self.lockfilename)
-                
+
                 # Here we save the PID in the lock file, but we don't use
                 # it, because the lock file may be in a directory shared
@@ -120,14 +120,14 @@
                 self.lockfile.write(str(self.pid))
                 self.lockfile.flush()
-            except IOError, msg :            
+            except IOError, msg :
                 self.logdebug("I/O Error while waiting for lock %s : %s" % (self.lockfilename, msg))
                 time.sleep(0.25)
-                    
-    def discoverOtherBackends(self) :    
+
+    def discoverOtherBackends(self) :
         """Discovers the other CUPS backends.
-        
+
            Executes each existing backend in turn in device enumeration mode.
            Returns the list of available backends.
-           
+
            Unfortunately this method can't output any debug information
            to stdout or stderr, else CUPS considers that the device is
@@ -151,5 +151,5 @@
                     # process doesn't exist anymore
                     os.remove(lockfilename)
-            
+
         if not os.path.exists(lockfilename) :
             lockfile = open(lockfilename, "w")
@@ -159,15 +159,15 @@
                                 for b in os.listdir(directory) \
                                     if os.access(os.path.join(directory, b), os.X_OK) \
-                                        and (b != myname)] 
-            for backend in allbackends :                            
+                                        and (b != myname)]
+            for backend in allbackends :
                 answer = os.popen(backend, "r")
                 try :
                     devices = [line.strip() for line in answer.readlines()]
-                except :    
+                except :
                     devices = []
                 status = answer.close()
                 if status is None :
                     for d in devices :
-                        # each line is of the form : 
+                        # each line is of the form :
                         # 'xxxx xxxx "xxxx xxx" "xxxx xxx"'
                         # so we have to decompose it carefully
@@ -186,7 +186,7 @@
                         try :
                             (devicetype, device, name, fullname) = arguments
-                        except ValueError :    
+                        except ValueError :
                             pass    # ignore this 'bizarre' device
-                        else :    
+                        else :
                             if name.startswith('"') and name.endswith('"') :
                                 name = name[1:-1]
@@ -202,6 +202,6 @@
                              % (self.myname, self.MyName, self.MyName))
         return available
-                        
-    def checkCUPSVersion(self) :                    
+
+    def checkCUPSVersion(self) :
         """Checks if CUPS is not v1.3.4 or higher."""
         fullversion = os.environ.get("SOFTWARE", "")
@@ -210,23 +210,23 @@
             try :
                (major, minor, release) = [int(p) for p in vnum.split(".")]
-            except ValueError :   
+            except ValueError :
                 pass
-            else :    
+            else :
                 return (major > 1) \
                         or ((major == 1) and (minor > 3)) \
                         or ((major == 1) and (minor == 3) and (release >= 4))
         return False
-        
-    def initBackendParameters(self) :    
+
+    def initBackendParameters(self) :
         """Initializes the backend's attributes."""
-        # check that the DEVICE_URI environment variable's value is 
+        # check that the DEVICE_URI environment variable's value is
         # prefixed with self.myname otherwise don't touch it.
-        # If this is the case, we have to remove the prefix from 
-        # the environment before launching the real backend 
+        # If this is the case, we have to remove the prefix from
+        # the environment before launching the real backend
         self.logdebug("Initializing backend...")
-        
+
         if not self.checkCUPSVersion() :
             self.printInfo("BEWARE : CUPS is too old. You should use CUPS v1.3.4 or higher.", "error")
-        
+
         self.PrinterName = os.environ.get("PRINTER", "")
         directories = [ self.config.getPrinterDirectory(self.PrinterName),
@@ -240,5 +240,5 @@
             else :
                 self.printInfo("Insufficient permissions to access to temporary directory %s" % direc, "warn")
-                
+
         self.Action = "ALLOW"   # job allowed by default
         self.Reason = None
@@ -247,19 +247,19 @@
             if copies < 1 :
                 raise ValueError
-        except (ValueError, TypeError) :    
+        except (ValueError, TypeError) :
             self.logdebug("Invalid number of copies '%s', using 1 instead." % sys.argv[4])
             copies = 1
         if len(sys.argv) == 7 :
             fname = sys.argv[6] # read job's datas from file
-        else :    
-            fname = None        # read job's datas from stdin            
-            
+        else :
+            fname = None        # read job's datas from stdin
+
         self.Ticket = cups.JobTicket(sys.argv[1].strip(), self.PrinterName, \
                                      copies, fname, sys.argv[5].strip())
-        self.UserName = self.Ticket.OriginatingUserName                             
-                                     
+        self.UserName = self.Ticket.OriginatingUserName
+
         self.DataFile = (os.path.join(self.Directory, "%s-%s-%s-%s" % \
                    (self.myname, self.PrinterName, self.UserName, self.Ticket.JobId))).encode(sys.getfilesystemencoding(), "replace")
-        
+
         muststartwith = "%s:" % self.myname
         device_uri = os.environ.get("DEVICE_URI", "")
@@ -268,39 +268,39 @@
             device_uri = fulldevice_uri[len(muststartwith):]
             for i in range(2) :
-                if device_uri.startswith("/") :  
+                if device_uri.startswith("/") :
                     device_uri = device_uri[1:]
         try :
-            (backend, destination) = device_uri.split(":", 1) 
-        except ValueError :    
+            (backend, destination) = device_uri.split(":", 1)
+        except ValueError :
             if not device_uri :
                 self.logdebug("Not attached to an existing print queue.")
                 backend = ""
                 printerhostname = ""
-            else :    
+            else :
                 raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
-        else :        
+        else :
             if backend == "hp" :
                 try :
                     printerhostname = destination.split("=")[1] # hp:/net/HP_LaserJet_8000_Series?ip=192.168.100.100
-                except IndexError :    
+                except IndexError :
                     self.logdebug("Unsupported hplip URI %s" % device_uri)
-                    printerhostname = "" 
-            else :    
+                    printerhostname = ""
+            else :
                 while destination.startswith("/") :
                     destination = destination[1:]
-                checkauth = destination.split("@", 1)    
+                checkauth = destination.split("@", 1)
                 if len(checkauth) == 2 :
                     destination = checkauth[1]
                 printerhostname = destination.split("/")[0].split(":")[0]
-            
-        self.PrinterHostName = printerhostname    
+
+        self.PrinterHostName = printerhostname
         self.RealBackend = backend
         self.DeviceURI = device_uri
-        
+
         if self.Ticket.BillingCode is None :
             self.OriginalJobBillingCode = None
-        else :    
+        else :
             self.OriginalJobBillingCode = self.Ticket.BillingCode[:]
-            
+
         baselockfilename = self.DeviceURI.replace("/", ".")
         baselockfilename = baselockfilename.replace(":", ".")
@@ -309,5 +309,5 @@
         baselockfilename = baselockfilename.replace("@", ".")
         self.lockfilename = os.path.join(self.Directory, "%s-%s..LCK" % (self.myname, baselockfilename))
-        
+
         self.logdebug("Backend : %s" % self.RealBackend)
         self.logdebug("DeviceURI : %s" % self.DeviceURI)
@@ -319,17 +319,17 @@
         self.logdebug("Copies : %s" % self.Ticket.Copies)
         self.logdebug("Options : %s" % self.Ticket.Options)
-        self.logdebug("Directory : %s" % self.Directory) 
+        self.logdebug("Directory : %s" % self.Directory)
         self.logdebug("DataFile : %s" % self.DataFile)
         self.logdebug("JobBillingCode : %s" % self.Ticket.BillingCode)
         self.logdebug("JobOriginatingHostName : %s" % self.Ticket.OriginatingHostName)
-        
+
         # fakes some entries to allow for external mailto
         # before real entries are extracted from the database.
         self.User = FakeUser(self.UserName)
         self.Printer = FakePrinter(self.PrinterName)
-        
+
         self.enableSigInt()
         self.logdebug("Backend initialized.")
-        
+
     def overwriteJobAttributes(self) :
         """Overwrites some of the job's attributes if needed."""
@@ -337,11 +337,11 @@
         # First overwrite the job ticket
         self.overwriteJobTicket()
-        
+
         # do we want to strip out the Samba/Winbind domain name ?
         separator = self.config.getWinbindSeparator()
         if separator is not None :
             self.UserName = self.UserName.split(separator)[-1]
-            
-        # this option is deprecated, and we want to tell people    
+
+        # this option is deprecated, and we want to tell people
         # this is the case.
         tolower = self.config.getUserNameToLower()
@@ -352,11 +352,11 @@
             if self.config.isTrue(tolower) :
                 self.UserName = self.UserName.lower()
-                
-        # Now use the newer and more complete 'usernamecase' directive.    
-        casechange = self.config.getUserNameCase()    
+
+        # Now use the newer and more complete 'usernamecase' directive.
+        casechange = self.config.getUserNameCase()
         if casechange != "native" :
             self.UserName = getattr(self.UserName, casechange)()
-            
-        # do we want to strip some prefix off of titles ?    
+
+        # do we want to strip some prefix off of titles ?
         stripprefix = self.config.getStripTitle(self.PrinterName)
         if stripprefix :
@@ -365,17 +365,17 @@
                                       % (stripprefix, self.Ticket.Title))
                 self.Ticket.Title = self.Ticket.Title[len(stripprefix):]
-                
+
         self.logdebug("Username : %s" % self.UserName)
         self.logdebug("BillingCode : %s" % self.Ticket.BillingCode)
         self.logdebug("Title : %s" % self.Ticket.Title)
         self.logdebug("Job's attributes sanitizing done.")
-                
+
     def didUserConfirm(self) :
         """Asks for user confirmation through an external script.
-        
+
            returns False if the end user wants to cancel the job, else True.
         """
         self.logdebug("Checking if we have to ask for user's confirmation...")
-        answer = None                         
+        answer = None
         confirmationcommand = self.config.getAskConfirmation(self.PrinterName)
         if confirmationcommand :
@@ -388,13 +388,13 @@
                     if answer == "CANCEL" :
                         break
-            except IOError, msg :            
+            except IOError, msg :
                 self.logdebug("IOError while reading subprocess' output : %s" % msg)
-            inputfile.close()    
+            inputfile.close()
             self.logdebug("User's confirmation received : %s" % (((answer == "CANCEL") and "CANCEL") or "CONTINUE"))
-        else :    
+        else :
             self.logdebug("No need to ask for user's confirmation, job processing will continue.")
-        return (answer != "CANCEL")    
-        
-    def overwriteJobTicket(self) :    
+        return (answer != "CANCEL")
+
+    def overwriteJobTicket(self) :
         """Should we overwrite the job's ticket (username and billingcode) ?"""
         self.logdebug("Checking if we need to overwrite the job ticket...")
@@ -414,8 +414,8 @@
                         self.logdebug("Seen CANCEL command.")
                         action = "CANCEL"
-                    elif line.startswith("USERNAME=") :    
+                    elif line.startswith("USERNAME=") :
                         username = line.split("=", 1)[1].strip().decode(self.charset, "replace")
                         self.logdebug("Seen new username [%s]" % username)
-                    elif line.startswith("BILLINGCODE=") :    
+                    elif line.startswith("BILLINGCODE=") :
                         billingcode = line.split("=", 1)[1].strip().decode(self.charset, "replace")
                         self.logdebug("Seen new billing code [%s]" % billingcode)
@@ -423,8 +423,8 @@
                         reason = line.split("=", 1)[1].strip().decode(self.charset, "replace")
                         self.logdebug("Seen new reason [%s]" % reason)
-            except IOError, msg :            
+            except IOError, msg :
                 self.logdebug("IOError while reading subprocess' output : %s" % msg)
-            inputfile.close()    
-            
+            inputfile.close()
+
             # now overwrite the job's ticket if new data was supplied
             if action == "DENY" :
@@ -441,17 +441,17 @@
                     self.UserName = username
                 if billingcode is not None :
-                    self.Ticket.BillingCode = billingcode 
+                    self.Ticket.BillingCode = billingcode
         self.logdebug("Job ticket overwriting done.")
-            
+
     def saveDatasAndCheckSum(self) :
         """Saves the input datas into a static file."""
         self.logdebug("Duplicating data stream into %s" % self.DataFile)
         mustclose = 0
-        outfile = open(self.DataFile, "wb")    
+        outfile = open(self.DataFile, "wb")
         if self.Ticket.FileName is not None :
             infile = open(self.Ticket.FileName, "rb")
             self.logdebug("Reading input datas from %s" % self.Ticket.FileName)
             mustclose = 1
-        else :    
+        else :
             infile = sys.stdin
             self.logdebug("Reading input datas from stdin")
@@ -461,24 +461,24 @@
         checksum = md5.new()
         while 1 :
-            data = infile.read(CHUNK) 
+            data = infile.read(CHUNK)
             if not data :
                 break
-            sizeread += len(data)    
+            sizeread += len(data)
             outfile.write(data)
-            checksum.update(data)    
+            checksum.update(data)
             if not (dummy % 32) : # Only display every 2 Mb
                 self.logdebug("%s bytes saved..." % sizeread)
-            dummy += 1    
-        if mustclose :    
+            dummy += 1
+        if mustclose :
             infile.close()
-            
+
         outfile.close()
-        self.JobSizeBytes = sizeread    
+        self.JobSizeBytes = sizeread
         self.JobMD5Sum = checksum.hexdigest()
-        
+
         self.logdebug("JobSizeBytes : %s" % self.JobSizeBytes)
         self.logdebug("JobMD5Sum : %s" % self.JobMD5Sum)
         self.logdebug("Data stream duplicated into %s" % self.DataFile)
-            
+
     def clean(self) :
         """Cleans up the place."""
@@ -487,5 +487,5 @@
             try :
                 keep = self.config.getPrinterKeepFiles(self.PrinterName)
-            except AttributeError :    
+            except AttributeError :
                 keep = False
             if not keep :
@@ -497,7 +497,7 @@
                 else :
                     self.logdebug("Work file %s has been deleted." % self.DataFile)
-            else :    
+            else :
                 self.logdebug("Work file %s will be kept." % self.DataFile)
-        PyKotaTool.clean(self)    
+        PyKotaTool.clean(self)
         if self.lockfile is not None :
             self.logdebug("Unlocking %s..." %  self.lockfilename)
@@ -505,11 +505,11 @@
                 fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
                 self.lockfile.close()
-            except :    
+            except :
                 self.printInfo("Problem while unlocking %s" % self.lockfilename, "error")
-            else :    
+            else :
                 self.logdebug("%s unlocked." % self.lockfilename)
         self.logdebug("Clean.")
-            
-    def precomputeJobSize(self) :    
+
+    def precomputeJobSize(self) :
         """Computes the job size with a software method."""
         self.logdebug("Precomputing job's size...")
@@ -518,6 +518,6 @@
         self.softwareJobSize = self.preaccounter.getJobSize(None)
         self.logdebug("Precomputed job's size is %s pages." % self.softwareJobSize)
-        
-    def precomputeJobPrice(self) :    
+
+    def precomputeJobPrice(self) :
         """Precomputes the job price with a software method."""
         self.logdebug("Precomputing job's price...")
@@ -525,6 +525,6 @@
         self.logdebug("Precomputed job's price is %.3f credits." \
                                    % self.softwareJobPrice)
-        
-    def exportJobInfo(self) :    
+
+    def exportJobInfo(self) :
         """Exports the actual job's attributes to the environment."""
         self.logdebug("Exporting job information to the environment...")
@@ -550,5 +550,5 @@
         setenv("PYKOTAPRECOMPUTEDJOBSIZE", str(self.softwareJobSize), self.charset)
         self.logdebug("Environment updated.")
-        
+
     def exportUserInfo(self) :
         """Exports user information to the environment."""
@@ -559,5 +559,5 @@
         setenv("PYKOTALIFETIMEPAID", str(self.User.LifeTimePaid or 0.0), self.charset)
         setenv("PYKOTAUSERDESCRIPTION", self.User.Description or "", self.charset)
-        
+
         setenv("PYKOTAPAGECOUNTER", str(self.UserPQuota.PageCounter or 0), self.charset)
         setenv("PYKOTALIFEPAGECOUNTER", str(self.UserPQuota.LifePageCounter or 0), self.charset)
@@ -566,10 +566,10 @@
         setenv("PYKOTADATELIMIT", str(self.UserPQuota.DateLimit), self.charset)
         setenv("PYKOTAWARNCOUNT", str(self.UserPQuota.WarnCount), self.charset)
-        
+
         # TODO : move this elsewhere once software accounting is done only once.
         setenv("PYKOTAPRECOMPUTEDJOBPRICE", str(self.softwareJobPrice), self.charset)
-        
+
         self.logdebug("Environment updated.")
-        
+
     def exportPrinterInfo(self) :
         """Exports printer information to the environment."""
@@ -584,5 +584,5 @@
         setenv("PYKOTAPRICEPERJOB", str(self.Printer.PricePerJob or 0), self.charset)
         self.logdebug("Environment updated.")
-        
+
     def exportPhaseInfo(self, phase) :
         """Exports phase information to the environment."""
@@ -590,5 +590,5 @@
         setenv("PYKOTAPHASE", phase, self.charset)
         self.logdebug("Environment updated.")
-        
+
     def exportJobSizeAndPrice(self) :
         """Exports job's size and price information to the environment."""
@@ -597,5 +597,5 @@
         setenv("PYKOTAJOBPRICE", str(self.JobPrice), self.charset)
         self.logdebug("Environment updated.")
-        
+
     def exportReason(self) :
         """Exports the job's action status and optional reason."""
@@ -605,16 +605,16 @@
             setenv("PYKOTAREASON", self.Reason or "", self.charset)
         self.logdebug("Environment updated.")
-        
-    def acceptJob(self) :        
+
+    def acceptJob(self) :
         """Returns the appropriate exit code to tell CUPS all is OK."""
         return 0
-            
-    def removeJob(self) :            
+
+    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 launchPreHook(self) :
         """Allows plugging of an external hook before the job gets printed."""
@@ -624,5 +624,5 @@
             retcode = os.system(prehook)
             self.logdebug("pre-hook exited with status %s." % retcode)
-        
+
     def launchPostHook(self) :
         """Allows plugging of an external hook after the job gets printed and/or denied."""
@@ -632,6 +632,6 @@
             retcode = os.system(posthook)
             self.logdebug("post-hook exited with status %s." % retcode)
-            
-    def improveMessage(self, message) :        
+
+    def improveMessage(self, message) :
         """Improves a message by adding more informations in it if possible."""
         try :
@@ -640,15 +640,15 @@
                                         self.Ticket.JobId, \
                                         message)
-        except :                                               
+        except :
             return message
-        
-    def logdebug(self, message) :        
+
+    def logdebug(self, message) :
         """Improves the debug message before outputting it."""
         PyKotaTool.logdebug(self, self.improveMessage(message))
-        
-    def printInfo(self, message, level="info") :        
+
+    def printInfo(self, message, level="info") :
         """Improves the informational message before outputting it."""
         self.logger.log_message(self.improveMessage(message), level)
-    
+
     def startingBanner(self, withaccounting) :
         """Retrieves a starting banner for current printer and returns its content."""
@@ -656,5 +656,5 @@
         self.printBanner(self.config.getStartingBanner(self.PrinterName), withaccounting)
         self.logdebug("Starting banner retrieved.")
-    
+
     def endingBanner(self, withaccounting) :
         """Retrieves an ending banner for current printer and returns its content."""
@@ -662,8 +662,8 @@
         self.printBanner(self.config.getEndingBanner(self.PrinterName), withaccounting)
         self.logdebug("Ending banner retrieved.")
-        
+
     def printBanner(self, bannerfileorcommand, withaccounting) :
         """Reads a banner or generates one through an external command.
-        
+
            Returns the banner's content in a format which MUST be accepted
            by the printer.
@@ -691,8 +691,8 @@
                 try :
                     fh = open(bannerfileorcommand, 'rb')
-                except IOError, msg :    
+                except IOError, msg :
                     self.printInfo("Impossible to open %s : %s" \
                                        % (bannerfileorcommand, msg), "error")
-                else :    
+                else :
                     self.runOriginalBackend(fh, isBanner=1)
                     fh.close()
@@ -701,10 +701,10 @@
                             self.BannerSize += 1 # TODO : fix this by passing the banner's content through software accounting
         self.logdebug("Banner printed...")
-                
+
     def handleBanner(self, bannertype, withaccounting) :
         """Handles the banner with or without accounting."""
         if withaccounting :
             acc = "with"
-        else :    
+        else :
             acc = "without"
         self.logdebug("Handling %s banner %s accounting..." % (bannertype, acc))
@@ -735,6 +735,6 @@
                         if (avoidduplicatebanners == "YES") :
                             printbanner = False
-                        else :    
-                            # avoidduplicatebanners is an integer, since NO, 
+                        else :
+                            # avoidduplicatebanners is an integer, since NO,
                             # YES and 0 are already handled
                             now = DateTime.now()
@@ -746,5 +746,5 @@
                             self.logdebug("Difference with previous job : %.2f seconds. Try to avoid banners for : %.2f seconds." % (difference, avoidduplicatebanners))
                             if difference < avoidduplicatebanners :
-                                self.logdebug("Duplicate banner avoided because previous banner is less than %.2f seconds old." % avoidduplicatebanners) 
+                                self.logdebug("Duplicate banner avoided because previous banner is less than %.2f seconds old." % avoidduplicatebanners)
                                 printbanner = False
                             else :
@@ -753,6 +753,6 @@
                     getattr(self, "%sBanner" % bannertype)(withaccounting)
         self.logdebug("%s banner done." % bannertype.title())
-        
-    def sanitizeJobSize(self) :    
+
+    def sanitizeJobSize(self) :
         """Sanitizes the job's size if needed."""
         # TODO : there's a difficult to see bug here when banner accounting is activated and hardware accounting is used.
@@ -772,15 +772,15 @@
                     if replacement == "PRECOMPUTED" :
                         self.JobSize = self.softwareJobSize
-                    else :    
+                    else :
                         self.JobSize = replacement
         self.logdebug("Job's size sanitized.")
-                        
-    def getPrinterUserAndUserPQuota(self) :        
+
+    def getPrinterUserAndUserPQuota(self) :
         """Returns a tuple (policy, printer, user, and user print quota) on this printer.
-        
+
            "OK" is returned in the policy if both printer, user and user print quota
            exist in the Quota Storage.
            Otherwise, the policy as defined for this printer in pykota.conf is returned.
-           
+
            If policy was set to "EXTERNAL" and one of printer, user, or user print quota
            doesn't exist in the Quota Storage, then an external command is launched, as
@@ -789,5 +789,5 @@
            or users, for example, and finally extracting printer, user and user print
            quota from the Quota Storage is tried a second time.
-           
+
            "EXTERNALERROR" is returned in case policy was "EXTERNAL" and an error status
            was returned by the external command.
@@ -802,5 +802,5 @@
                 break
             (policy, args) = self.config.getPrinterPolicy(self.PrinterName)
-            if policy == "EXTERNAL" :    
+            if policy == "EXTERNAL" :
                 commandline = self.formatCommandLine(args, user, printer)
                 if not printer.Exists :
@@ -814,5 +814,5 @@
                     policy = "EXTERNALERROR"
                     break
-            else :        
+            else :
                 if not printer.Exists :
                     self.printInfo(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.PrinterName, policy))
@@ -822,6 +822,6 @@
                     self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.UserName, self.PrinterName, policy))
                 break
-                
-        if policy == "EXTERNAL" :    
+
+        if policy == "EXTERNAL" :
             if not printer.Exists :
                 self.printInfo(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.PrinterName)
@@ -830,13 +830,13 @@
             if not userpquota.Exists :
                 self.printInfo(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.UserName, self.PrinterName))
-        self.Policy = policy         
+        self.Policy = policy
         self.Printer = printer
         self.User = user
         self.UserPQuota = userpquota
         self.logdebug("Retrieval of printer, user and user print quota entry done.")
-        
-    def getBillingCode(self) :    
+
+    def getBillingCode(self) :
         """Extracts the billing code from the database.
-         
+
            An optional script is launched to notify the user when
            the billing code is unknown and PyKota was configured to
@@ -858,15 +858,15 @@
                     if self.BillingCode.Exists :
                         self.logdebug(msg + "has been created.")
-                    else :    
+                    else :
                         self.printInfo(msg + "couldn't be created.", "error")
-                else :    
+                else :
                     self.logdebug(msg + "job will be denied.")
                     self.Action = newaction
-                    if script is not None : 
+                    if script is not None :
                         self.logdebug(msg + "launching subprocess [%s] to notify user." % script)
                         os.system(script)
         self.logdebug("Retrieval of billing code information done.")
-        
-    def checkIfDupe(self) :    
+
+    def checkIfDupe(self) :
         """Checks if the job is a duplicate, and handles the situation."""
         self.logdebug("Checking if the job is a duplicate...")
@@ -889,8 +889,8 @@
                     self.logdebug("Duplicate job allowed because previous one is more than %.2f seconds old." % duplicatesdelay)
                 else :
-                    # TODO : use the current user's last job instead of  
+                    # TODO : use the current user's last job instead of
                     # TODO : the current printer's last job. This would be
                     # TODO : better but requires an additional database query
-                    # TODO : with SQL, and is much more complex with the 
+                    # TODO : with SQL, and is much more complex with the
                     # TODO : actual LDAP schema. Maybe this is not very
                     # TODO : important, because usually duplicate jobs are sucessive.
@@ -900,29 +900,29 @@
                         self.Action = "DENY"
                         self.Reason = _("Duplicate print jobs are not allowed on printer %s.") % self.PrinterName
-                    else :    
+                    else :
                         self.logdebug("Launching subprocess [%s] to see if duplicate jobs should be allowed or not." % denyduplicates)
                         fanswer = os.popen(denyduplicates, "r")
                         self.Action = fanswer.read().strip().upper()
                         fanswer.close()
-                        if self.Action == "DENY" :     
+                        if self.Action == "DENY" :
                             self.printInfo("%s : %s." % (msg, _("Subprocess denied printing of a dupe")), "warn")
                             self.Reason = _("Duplicate print jobs are not allowed on printer %s at this time.") % self.PrinterName
-                        else :    
+                        else :
                             self.printInfo("%s : %s." % (msg, _("Subprocess allowed printing of a dupe")), "warn")
-            else :            
+            else :
                 self.logdebug("Job doesn't seem to be a duplicate.")
         self.logdebug("Checking if the job is a duplicate done.")
-        
+
     def tellUser(self) :
         """Sends a message to an user."""
-        self.logdebug("Sending some feedback to user %s..." % self.UserName)  
+        self.logdebug("Sending some feedback to user %s..." % self.UserName)
         if not self.Reason :
             self.logdebug("No feedback to send to user %s." % self.UserName)
-        else :    
+        else :
             (mailto, arguments) = self.config.getMailTo(self.PrinterName)
             if mailto == "EXTERNAL" :
                 # TODO : clean this again
                 self.externalMailTo(arguments, self.Action, self.User, self.Printer, self.Reason)
-            else :    
+            else :
                 # TODO : clean this again
                 admin = self.config.getAdmin(self.PrinterName)
@@ -934,11 +934,11 @@
                 if mailto in ("BOTH", "ADMIN") :
                     destination.append(adminmail)
-                if mailto in ("BOTH", "USER") :    
+                if mailto in ("BOTH", "USER") :
                     destination.append(usermail)
-                    
+
                 fullmessage = self.Reason + (_("\n\nYour system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail))
-                try :    
+                try :
                     server = smtplib.SMTP(self.smtpserver)
-                except socket.error, msg :    
+                except socket.error, msg :
                     self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
                 else :
@@ -951,9 +951,9 @@
                             if mailto == "BOTH" :
                                 msg["Cc"] = adminmail
-                        else :    
+                        else :
                             msg["To"] = adminmail
                         msg["Date"] = email.Utils.formatdate(localtime=True)
                         server.sendmail(adminmail, destination, msg.as_string())
-                    except smtplib.SMTPException, answer :    
+                    except smtplib.SMTPException, answer :
                         try :
                             for (k, v) in answer.recipients.items() :
@@ -963,6 +963,6 @@
                     server.quit()
             self.logdebug("Feedback sent to user %s." % self.UserName)
-                
-    def mainWork(self) :    
+
+    def mainWork(self) :
         """Main work is done here."""
         if not self.JobSizeBytes :
@@ -974,5 +974,5 @@
             self.tellUser()
             return self.removeJob()
-            
+
         self.getPrinterUserAndUserPQuota()
         if self.Policy == "EXTERNALERROR" :
@@ -989,5 +989,5 @@
             self.tellUser()
             return self.removeJob()
-        elif self.Policy == "DENY" :    
+        elif self.Policy == "DENY" :
             # Either printer, user or user print quota doesn't exist,
             # and the job should be rejected.
@@ -1007,11 +1007,11 @@
             #            be allowed if current user is allowed to print on this printer
             return self.doWork()
-        else :    
+        else :
             self.Reason = _("Invalid policy %s for printer %s") % (self.Policy, self.PrinterName)
             self.printInfo(self.Reason, "error")
             self.tellUser()
             return self.removeJob()
-    
-    def doWork(self) :    
+
+    def doWork(self) :
         """The accounting work is done here."""
         self.precomputeJobPrice()
@@ -1019,6 +1019,6 @@
         self.exportPrinterInfo()
         self.exportPhaseInfo("BEFORE")
-        
-        if self.Action not in ("DENY", "CANCEL") : 
+
+        if self.Action not in ("DENY", "CANCEL") :
             if self.Printer.MaxJobSize and (self.softwareJobSize > self.Printer.MaxJobSize) :
                 # This printer was set to refuse jobs this large.
@@ -1028,5 +1028,5 @@
                 # because in case of error the user could complain :-)
                 self.Reason = _("You are not allowed to print so many pages on printer %s at this time.") % self.PrinterName
-            
+
         if self.Action not in ("DENY", "CANCEL") :
             if self.User.LimitBy == "noprint" :
@@ -1034,5 +1034,5 @@
                 self.Action = "DENY"
                 self.Reason = _("Your account settings forbid you to print at this time.")
-                
+
         if self.Action not in ("DENY", "CANCEL") :
             # If printing is still allowed at this time, we
@@ -1041,5 +1041,5 @@
             # save some database queries.
             self.getBillingCode()
-            
+
         if self.Action not in ("DENY", "CANCEL") :
             # If printing is still allowed at this time, we
@@ -1048,5 +1048,5 @@
             # save some database queries.
             self.checkIfDupe()
-                    
+
         if self.Action not in ("DENY", "CANCEL") :
             # If printing is still allowed at this time, we
@@ -1056,5 +1056,5 @@
             if self.User.LimitBy in ('noquota', 'nochange') :
                 self.logdebug("User %s is allowed to print with no limit, no need to check quota." % self.UserName)
-            elif self.Printer.PassThrough :    
+            elif self.Printer.PassThrough :
                 self.logdebug("Printer %s is in PassThrough mode, no need to check quota." % self.PrinterName)
             else :
@@ -1067,27 +1067,27 @@
                     self.printInfo(_("Print Quota exceeded for user %s on printer %s") % (self.UserName, self.PrinterName))
                     self.Reason = self.config.getHardWarn(self.PrinterName)
-                elif self.Action == "WARN" :    
+                elif self.Action == "WARN" :
                     self.printInfo(_("Print Quota low for user %s on printer %s") % (self.UserName, self.PrinterName))
-                    if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") : 
+                    if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") :
                         self.Reason = self.config.getPoorWarn()
-                    else :     
+                    else :
                         self.Reason = self.config.getSoftWarn(self.PrinterName)
-            
-        # If job still allowed to print, should we ask for confirmation ?    
-        if self.Action not in ("DENY", "CANCEL") : 
+
+        # If job still allowed to print, should we ask for confirmation ?
+        if self.Action not in ("DENY", "CANCEL") :
             if not self.didUserConfirm() :
                 self.Action = "CANCEL"
                 self.Reason = _("Print job cancelled.")
                 setenv("PYKOTASTATUS", "CANCELLED", self.charset)
-                
+
         # exports some new environment variables
         self.exportReason()
-        
+
         # now tell the user if he needs to know something
         self.tellUser()
-        
+
         # launches the pre hook
         self.launchPreHook()
-        
+
         # handle starting banner pages without accounting
         self.BannerSize = 0
@@ -1095,40 +1095,40 @@
         if (self.Action != "CANCEL") and accountbanner in ["ENDING", "NONE"] :
             self.handleBanner("starting", 0)
-        
+
         if self.Action == "DENY" :
             self.printInfo(_("Job denied, no accounting will be done."))
-        elif self.Action == "CANCEL" :    
+        elif self.Action == "CANCEL" :
             self.printInfo(_("Job cancelled, no accounting will be done."))
         else :
             self.printInfo(_("Job accounting begins."))
             self.accounter.beginJob(self.Printer)
-        
+
         # handle starting banner pages with accounting
         if (self.Action != "CANCEL") and accountbanner in ["STARTING", "BOTH"] :
             self.handleBanner("starting", 1)
-        
-        # pass the job's data to the real backend if needed   
+
+        # pass the job's data to the real backend if needed
         if self.Action in ("ALLOW", "WARN") :
             retcode = self.printJobDatas()
-        else :        
+        else :
             retcode = self.removeJob()
-        
+
         # indicate phase change
         self.exportPhaseInfo("AFTER")
-        
+
         # handle ending banner pages with accounting
         if (self.Action != "CANCEL") and accountbanner in ["ENDING", "BOTH"] :
             self.handleBanner("ending", 1)
-        
+
         # stops accounting
         if self.Action == "DENY" :
             self.printInfo(_("Job denied, no accounting has been done."))
-        elif self.Action == "CANCEL" :    
+        elif self.Action == "CANCEL" :
             self.printInfo(_("Job cancelled, no accounting has been done."))
         else :
             self.accounter.endJob(self.Printer)
             self.printInfo(_("Job accounting ends."))
-        
-        # Do all these database changes within a single transaction    
+
+        # Do all these database changes within a single transaction
         # NB : we don't enclose ALL the changes within a single transaction
         # because while waiting for the printer to answer its internal page
@@ -1147,22 +1147,22 @@
                     self.JobSize = 0
                     self.printInfo(_("Job size forced to 0 because the real CUPS backend failed. No accounting will be done."), "warn")
-                else :    
+                else :
                     self.printInfo(_("The real CUPS backend failed, but the job will be accounted for anyway."), "warn")
-                    
-            # retrieve the job size    
+
+            # retrieve the job size
             if self.Action == "DENY" :
                 self.JobSize = 0
                 self.printInfo(_("Job size forced to 0 because printing is denied."))
-            elif self.Action == "CANCEL" :     
+            elif self.Action == "CANCEL" :
                 self.JobSize = 0
                 self.printInfo(_("Job size forced to 0 because printing was cancelled."))
-            else :    
+            else :
                 self.UserPQuota.resetDenyBannerCounter()
-                if (self.Action != "PROBLEM") or ("CHARGE" in onbackenderror) : 
+                if (self.Action != "PROBLEM") or ("CHARGE" in onbackenderror) :
                     self.JobSize = self.accounter.getJobSize(self.Printer)
                     self.sanitizeJobSize()
                     self.JobSize += self.BannerSize
             self.printInfo(_("Job size : %i") % self.JobSize)
-            
+
             if ((self.Action == "PROBLEM") and ("NOCHARGE" in onbackenderror)) or \
                 (self.Action in ("DENY", "CANCEL")) :
@@ -1173,9 +1173,9 @@
                 self.JobPrice = 0.0
             else :
-                # update the quota for the current user on this printer 
+                # update the quota for the current user on this printer
                 self.printInfo(_("Updating user %s's quota on printer %s") % (self.UserName, self.PrinterName))
                 self.JobPrice = self.UserPQuota.increasePagesUsage(self.JobSize, self.accounter.inkUsage)
-            
-            # adds the current job to history    
+
+            # adds the current job to history
             self.Printer.addJobToHistory(self.Ticket.JobId, self.User, self.accounter.getLastPageCounter(), \
                                     self.Action, self.JobSize, self.JobPrice, self.Ticket.FileName, \
@@ -1184,5 +1184,5 @@
                                     self.softwareJobSize, self.softwareJobPrice)
             self.printInfo(_("Job added to history."))
-            
+
             if hasattr(self, "BillingCode") and self.BillingCode and self.BillingCode.Exists :
                 if (self.Action in ("ALLOW", "WARN")) or \
@@ -1190,28 +1190,28 @@
                     self.BillingCode.consume(self.JobSize, self.JobPrice)
                     self.printInfo(_("Billing code %s was updated.") % self.BillingCode.BillingCode)
-        except :    
+        except :
             self.storage.rollbackTransaction()
             raise
-        else :    
+        else :
             self.storage.commitTransaction()
-            
+
         # exports some new environment variables
         self.exportJobSizeAndPrice()
-        
+
         # then re-export user information with new values
         self.exportUserInfo()
-        
+
         # handle ending banner pages without accounting
         if (self.Action != "CANCEL") and accountbanner in ["STARTING", "NONE"] :
             self.handleBanner("ending", 0)
-                    
+
         self.launchPostHook()
-            
-        return retcode    
-               
-    def printJobDatas(self) :           
+
+        return retcode
+
+    def printJobDatas(self) :
         """Sends the job's datas to the real backend."""
         self.logdebug("Sending job's datas to real backend...")
-        
+
         delay = 0
         number = 1
@@ -1222,15 +1222,15 @@
                     if (number < 0) or (delay < 0) :
                         raise ValueError
-                except ValueError :    
+                except ValueError :
                     self.printInfo(_("Incorrect value for the 'onbackenderror' directive in section [%s]") % self.PrinterName, "error")
                     delay = 0
                     number = 1
-                else :    
+                else :
                     break
-        loopcnt = 1 
-        while True :            
+        loopcnt = 1
+        while True :
             if self.Ticket.FileName is None :
                 infile = open(self.DataFile, "rb")
-            else :    
+            else :
                 infile = None
             retcode = self.runOriginalBackend(infile)
@@ -1244,10 +1244,10 @@
                     time.sleep(delay)
                     loopcnt += 1
-                else :    
+                else :
                     break
-            
+
         self.logdebug("Job's datas sent to real backend.")
         return retcode
-        
+
     def runOriginalBackend(self, filehandle=None, isBanner=0) :
         """Launches the original backend."""
@@ -1255,5 +1255,5 @@
         if not isBanner :
             arguments = [os.environ["DEVICE_URI"]] + [a.encode("UTF-8") for a in sys.argv[1:]]
-        else :    
+        else :
             # For banners, we absolutely WANT
             # to remove any filename from the command line !
@@ -1264,5 +1264,5 @@
         # TODO : do something about the job title : if we are printing a banner and the backend
         # TODO : uses the job's title to name an output file (cups-pdf:// for example), we're stuck !
-        
+
         self.logdebug("Starting original backend %s with args %s" % (originalbackend, " ".join(['"%s"' % a.decode("UTF-8") for a in arguments])))
         pid = os.fork()
@@ -1277,8 +1277,8 @@
             except OSError, msg :
                 self.logdebug("execve() failed: %s" % msg)
-            self.logdebug("We shouldn't be there !!!")    
+            self.logdebug("We shouldn't be there !!!")
             os._exit(-1)
-        
-        self.logdebug("Waiting for original backend to exit...")    
+
+        self.logdebug("Waiting for original backend to exit...")
         killed = False
         status = -1
@@ -1289,5 +1289,5 @@
                 if err == 4 :
                     killed = True
-                    
+
         if os.WIFEXITED(status) :
             status = os.WEXITSTATUS(status)
@@ -1297,5 +1297,5 @@
                 level = "error"
                 self.Reason = message
-            else :    
+            else :
                 level = "info"
             self.printInfo(message, level)
@@ -1309,16 +1309,16 @@
             self.printInfo(self.Reason, "warn")
             return 1
-        
-if __name__ == "__main__" :    
+
+if __name__ == "__main__" :
     # This is a CUPS backend, we should act and die like a CUPS backend
     wrapper = CUPSBackend()
     if len(sys.argv) == 1 :
         print "\n".join(wrapper.discoverOtherBackends())
-        sys.exit(0)                
-    elif len(sys.argv) not in (6, 7) :    
+        sys.exit(0)
+    elif len(sys.argv) not in (6, 7) :
         logerr("ERROR: %s job-id user title copies options [file]\n"\
                               % sys.argv[0])
         sys.exit(1)
-    else :    
+    else :
         os.environ["PATH"] = "%s:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin" % os.environ.get("PATH", "")
         try :
@@ -1333,20 +1333,20 @@
                 wrapper.accounter = openAccounter(wrapper)
                 wrapper.precomputeJobSize()
-                wrapper.exportJobInfo() # exports a first time to give hints to external scripts 
+                wrapper.exportJobInfo() # exports a first time to give hints to external scripts
                 wrapper.overwriteJobAttributes()
                 wrapper.exportJobInfo() # re-exports in case it was overwritten
                 retcode = wrapper.mainWork()
-            except KeyboardInterrupt :    
+            except KeyboardInterrupt :
                 wrapper.printInfo(_("Job %s interrupted by the administrator !") % wrapper.Ticket.JobId, "warn")
                 retcode = 0
-            except SystemExit, err :    
+            except SystemExit, err :
                 retcode = err.code
-            except :    
+            except :
                 try :
                     wrapper.crashed("cupspykota backend failed")
-                except :    
+                except :
                     crashed("cupspykota backend failed")
                 retcode = 1
-        finally :        
+        finally :
             wrapper.clean()
         sys.exit(retcode)
