Index: pykota/trunk/bin/edpykota
===================================================================
--- pykota/trunk/bin/edpykota (revision 3411)
+++ pykota/trunk/bin/edpykota (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/>.
@@ -38,5 +38,5 @@
 
   edpykota [options] user1 user2 ... userN
-  
+
   edpykota [options] group1 group2 ... groupN
 
@@ -45,8 +45,8 @@
   -v | --version       Prints edpykota's version number then exits.
   -h | --help          Prints this message then exits.
-  
+
   -a | --add           Adds users or groups print quota entries if
                        they don't exist in database.
-                       
+
   -d | --delete        Deletes users or groups print quota entries.
                        Users or groups are never deleted, you have
@@ -54,35 +54,35 @@
                        The history will be purge from all matching
                        jobs, unless -g | --groups is used.
-  
+
   -P | --printer p     Edit quotas on printer p only. Actually p can
                        use wildcards characters to select only
                        some printers. The default value is *, meaning
-                       all printers. 
-                       You can specify several names or wildcards, 
+                       all printers.
+                       You can specify several names or wildcards,
                        by separating them with commas.
-  
-  -g | --groups        Edit groups print quota entries instead of 
+
+  -g | --groups        Edit groups print quota entries instead of
                        users print quota entries.
-                          
+
   -L | --list          Lists users or groups print quota entries.
-  
+
   -n | --noquota       Sets both soft and hard limits to None for users
                        or groups print quota entries.
-  
+
   -r | --reset         Resets the actual page counter for the user
-                       or group to zero on the specified printers. 
+                       or group to zero on the specified printers.
                        The life time page counter is kept unchanged.
-                       
+
   -R | --hardreset     Resets the actual and life time page counters
-                       for the user or group to zero on the specified 
+                       for the user or group to zero on the specified
                        printers. This is a shortcut for '--used 0'.
-                       
+
   -s | --skipexisting  In combination with the --add option above, tells
                        edpykota to not modify existing print quota entries.
-                       
-  -S | --softlimit sl  Sets the quota soft limit to sl pages.                       
-  
+
+  -S | --softlimit sl  Sets the quota soft limit to sl pages.
+
   -H | --hardlimit hl  Sets the quota hard limit to hl pages.
-  
+
   -I | --increase v    Increase existing Soft and Hard limits by the value
                        of v. You can prefix v with + or -, if no sign is
@@ -104,15 +104,15 @@
   user1 through userN and group1 through groupN can use wildcards
   if the --add option is not set.
-  
-examples :                              
+
+examples :
 
   $ edpykota --add john paul george ringo
-  
+
   This will create print quota entries for users john, paul, george
   and ringo on all printers. These print quota entries will have no
   limit set.
-  
+
   $ edpykota --printer lp -S 50 -H 60 jerome
-  
+
   This will set jerome's print quota on the lp printer to a soft limit
   of 50 pages, and a hard limit of 60 pages. Both user jerome and
@@ -120,11 +120,11 @@
   commands, respectively.
 
-  $ edpykota -g -S 500 -H 550 financial support            
-  
+  $ edpykota -g -S 500 -H 550 financial support
+
   This will set print quota soft limit to 500 pages and hard limit
   to 550 pages for groups financial and support on all printers.
-  
+
   $ edpykota --reset jerome "jo*"
-  
+
   This will reset jerome's page counter to zero on all printers, as
   well as every user whose name begins with 'jo'.
@@ -132,21 +132,21 @@
   You can also reset the life time page counters by using the
   --hardreset | -R command line option.
-  
+
   $ edpykota --printer hpcolor --noquota jerome
-  
-  This will tell PyKota to not limit jerome when printing on the 
-  hpcolor printer. All his jobs will be allowed on this printer, but 
+
+  This will tell PyKota to not limit jerome when printing on the
+  hpcolor printer. All his jobs will be allowed on this printer, but
   accounting of the pages he prints will still be kept.
   Print Quotas for jerome on other printers are unchanged.
-  
+
   $ edpykota --delete --printer "HP*,XER*" jerome rachel
-  
+
   This will delete users jerome and rachel's print quota
   entries on all printers which name begin with 'HP' or
   'XER'. The jobs printed by these users on these printers
   will be deleted from the history.
-""") 
-        
-class EdPyKota(PyKotaTool) :        
+""")
+
+class EdPyKota(PyKotaTool) :
     """A class for edpykota."""
     def modifyPQEntry(self, pqkey, pqentry, noquota, softlimit, hardlimit, increase, reset, hardreset, suffix, used) :
@@ -155,24 +155,24 @@
             pqentry.setLimits(softlimit, hardlimit)
         if increase :
-            newsoft = (pqentry.SoftLimit or 0) + increase         
-            newhard = (pqentry.HardLimit or 0) + increase         
+            newsoft = (pqentry.SoftLimit or 0) + increase
+            newhard = (pqentry.HardLimit or 0) + increase
             if (newsoft >= 0) and (newhard >= 0) :
                 pqentry.setLimits(newsoft, newhard)
-            else :    
+            else :
                 self.printInfo(_("You can't set negative limits for %s") % pqkey, "error")
         if reset :
             pqentry.reset()
-        if hardreset :    
+        if hardreset :
             pqentry.hardreset()
         if suffix == "User" :
             if used :
                 pqentry.setUsage(used)
-    
+
     def main(self, names, options) :
         """Edit user or group quotas."""
         names = self.sanitizeNames(options, names)
-        suffix = (options["groups"] and "Group") or "User"        
+        suffix = (options["groups"] and "Group") or "User"
         printernames = options["printer"].split(",")
-            
+
         if not options["list"] :
             percent = Percent(self)
@@ -182,5 +182,5 @@
         if not options["list"] :
             percent.setSize(len(printers) * len(entries))
-        
+
         if options["list"] :
             for printer in printers :
@@ -198,5 +198,5 @@
                             print "    %s" % (_("Warning banners printed : %s") % pqentry.WarnCount)
                         print
-        elif options["delete"] :    
+        elif options["delete"] :
             percent.display("\n%s..." % _("Deletion"))
             getattr(self.storage, "deleteMany%sPQuotas" % suffix)(printers, entries)
@@ -211,5 +211,5 @@
                 except ValueError :
                     raise PyKotaCommandLineError, _("Invalid used value %s.") % used
-                    
+
             increase = options["increase"]
             if increase :
@@ -218,7 +218,7 @@
                 except ValueError :
                     raise PyKotaCommandLineError, _("Invalid increase value %s.") % increase
-            
+
             noquota = options["noquota"]
-            reset = options["reset"]        
+            reset = options["reset"]
             hardreset = options["hardreset"]
             softlimit = hardlimit = None
@@ -229,5 +229,5 @@
                         if softlimit < 0 :
                             raise ValueError
-                    except ValueError :    
+                    except ValueError :
                         raise PyKotaCommandLineError, _("Invalid softlimit value %s.") % options["softlimit"]
                 if options["hardlimit"] :
@@ -236,26 +236,26 @@
                         if hardlimit < 0 :
                             raise ValueError
-                    except ValueError :    
+                    except ValueError :
                         raise PyKotaCommandLineError, _("Invalid hardlimit value %s.") % options["hardlimit"]
-                if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :        
+                if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :
                     # error, exchange them
                     self.printInfo(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
                     (softlimit, hardlimit) = (hardlimit, softlimit)
-                if hardlimit is None :    
+                if hardlimit is None :
                     hardlimit = softlimit
                     if hardlimit is not None :
                         self.printInfo(_("Undefined hard limit set to soft limit (%s).") % str(hardlimit))
-                if softlimit is None :    
+                if softlimit is None :
                     softlimit = hardlimit
                     if softlimit is not None :
                         self.printInfo(_("Undefined soft limit set to hard limit (%s).") % str(softlimit))
-                        
-            self.storage.beginTransaction()            
+
+            self.storage.beginTransaction()
             try :
                 if options["add"] :
                     percent.display("\n%s...\n" % _("Creation"))
-                    if not entries :    
+                    if not entries :
                         self.printInfo(_("No entry matches %s. Please use pkusers to create them first.") % (" ".join(names)), "warn")
-                            
+
                     factory = globals()["Storage%sPQuota" % suffix]
                     for printer in printers :
@@ -270,8 +270,8 @@
                                                         hardreset, suffix, used)
                             oldpqentry = getattr(self.storage, "add%sPQuota" % suffix)(pqentry)
-                            if oldpqentry is not None :    
+                            if oldpqentry is not None :
                                 if skipexisting :
                                     self.logdebug("%s print quota entry %s@%s already exists, skipping." % (suffix, ename, pname))
-                                else :    
+                                else :
                                     self.logdebug("%s print quota entry %s@%s already exists, will be modified." % (suffix, ename, pname))
                                     self.modifyPQEntry(pqkey, oldpqentry, noquota, \
@@ -279,7 +279,7 @@
                                                         increase, reset, \
                                                         hardreset, suffix, used)
-                                    oldpqentry.save()                    
+                                    oldpqentry.save()
                             percent.oneMore()
-                else :        
+                else :
                     percent.display("\n%s...\n" % _("Modification"))
                     for printer in printers :
@@ -287,21 +287,21 @@
                             pqkey = "%s@%s" % (entry.Name, printer.Name)
                             pqentry = getattr(self.storage, "get%sPQuota" % suffix)(entry, printer)
-                            if pqentry.Exists :     
+                            if pqentry.Exists :
                                 self.modifyPQEntry(pqkey, pqentry, noquota, \
                                                     softlimit, hardlimit, \
                                                     increase, reset, \
                                                     hardreset, suffix, used)
-                                pqentry.save()        
+                                pqentry.save()
                             percent.oneMore()
-            except :                    
+            except :
                 self.storage.rollbackTransaction()
                 raise
-            else :    
+            else :
                 self.storage.commitTransaction()
-                            
+
         if not options["list"] :
             percent.done()
-            
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     retcode = 0
     try :
@@ -316,12 +316,12 @@
                         "printer=", "softlimit=", "hardlimit=", \
                         "increase=", "used=", "skipexisting"]
-        
+
         # Initializes the command line tool
         manager = EdPyKota(doc=__doc__)
         manager.deferredInit()
-        
+
         # parse and checks the command line
         (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
-        
+
         # sets long options
         options["help"] = options["h"] or options["help"]
@@ -331,14 +331,14 @@
         options["printer"] = options["P"] or options["printer"] or defaults["printer"]
         options["softlimit"] = options["S"] or options["softlimit"]
-        options["hardlimit"] = options["H"] or options["hardlimit"] 
-        options["reset"] = options["r"] or options["reset"] 
+        options["hardlimit"] = options["H"] or options["hardlimit"]
+        options["reset"] = options["r"] or options["reset"]
         options["noquota"] = options["n"] or options["noquota"]
-        options["delete"] = options["d"] or options["delete"] 
-        options["hardreset"] = options["R"] or options["hardreset"] 
+        options["delete"] = options["d"] or options["delete"]
+        options["hardreset"] = options["R"] or options["hardreset"]
         options["used"] = options["U"] or options["used"]
         options["increase"] = options["I"] or options["increase"]
         options["list"] = options["L"] or options["list"]
         options["skipexisting"] = options["s"] or options["skipexisting"]
-        
+
         if options["help"] :
             manager.display_usage_and_quit()
@@ -354,16 +354,16 @@
         else :
             retcode = manager.main(args, options)
-    except KeyboardInterrupt :        
+    except KeyboardInterrupt :
         logerr("\nInterrupted with Ctrl+C !\n")
         retcode = -3
-    except PyKotaCommandLineError, msg :     
+    except PyKotaCommandLineError, msg :
         logerr("%s : %s\n" % (sys.argv[0], msg))
         retcode = -2
-    except SystemExit :        
+    except SystemExit :
         pass
     except :
         try :
             manager.crashed("edpykota failed")
-        except :    
+        except :
             crashed("edpykota failed")
         retcode = -1
@@ -371,6 +371,6 @@
     try :
         manager.storage.close()
-    except (TypeError, NameError, AttributeError) :    
+    except (TypeError, NameError, AttributeError) :
         pass
-        
-    sys.exit(retcode)    
+
+    sys.exit(retcode)
Index: pykota/trunk/bin/waitprinter.sh
===================================================================
--- pykota/trunk/bin/waitprinter.sh (revision 3389)
+++ pykota/trunk/bin/waitprinter.sh (revision 3413)
@@ -8,10 +8,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/>.
Index: pykota/trunk/bin/papwaitprinter.sh
===================================================================
--- pykota/trunk/bin/papwaitprinter.sh (revision 3275)
+++ pykota/trunk/bin/papwaitprinter.sh (revision 3413)
@@ -8,10 +8,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/>.
@@ -21,5 +21,5 @@
 PATH=$PATH:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin
 sleep 5;
-until papstatus -p "$1" | grep -i idle >/dev/null ; do 
-   sleep 1 ; 
+until papstatus -p "$1" | grep -i idle >/dev/null ; do
+   sleep 1 ;
 done
Index: pykota/trunk/bin/pkinvoice
===================================================================
--- pykota/trunk/bin/pkinvoice (revision 3411)
+++ pykota/trunk/bin/pkinvoice (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/>.
@@ -34,14 +34,14 @@
     from reportlab.lib import pagesizes
     from reportlab.lib.units import cm
-except ImportError :    
+except ImportError :
     hasRL = False
-else :    
+else :
     hasRL = True
-    
+
 try :
-    import PIL.Image 
-except ImportError :    
+    import PIL.Image
+except ImportError :
     hasPIL = False
-else :    
+else :
     hasPIL = True
 
@@ -56,5 +56,5 @@
 from pykota.tool import Percent, PyKotaTool
 
-class PKInvoice(PyKotaTool) :        
+class PKInvoice(PyKotaTool) :
     """A class for invoice generator."""
     validfilterkeys = [ "username",
@@ -66,10 +66,10 @@
                         "end",
                       ]
-                      
+
     def printVar(self, label, value, size) :
         """Outputs a variable onto the PDF canvas.
-        
+
            Returns the number of points to substract to current Y coordinate.
-        """   
+        """
         xcenter = (self.pagesize[0] / 2.0) - 1*cm
         self.canvas.saveState()
@@ -82,5 +82,5 @@
         self.canvas.restoreState()
         self.ypos -= (size + 4)
-        
+
     def pagePDF(self, invoicenumber, name, values, unit, vat) :
         """Generates a new page in the PDF document."""
@@ -96,5 +96,5 @@
             datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
             self.printVar(_("Edited on"), datetime, 14)
-                
+
             self.ypos -= 20
             self.printVar(_("Number of jobs printed"), str(values["nbjobs"]), 18)
@@ -107,31 +107,31 @@
             self.canvas.showPage()
             return 1
-        return 0    
-        
+        return 0
+
     def initPDF(self, logo) :
         """Initializes the PDF document."""
-        self.pdfDocument = cStringIO.StringIO()        
+        self.pdfDocument = cStringIO.StringIO()
         self.canvas = c = canvas.Canvas(self.pdfDocument, \
                                         pagesize=self.pagesize, \
                                         pageCompression=1)
-        
+
         c.setAuthor(self.effectiveUserName)
         c.setTitle(_("PyKota invoices"))
         c.setSubject(_("Invoices generated with PyKota"))
-        
+
         self.canvas.beginForm("background")
         self.canvas.saveState()
-        
-        self.ypos = self.pagesize[1] - (2 * cm)            
-        
+
+        self.ypos = self.pagesize[1] - (2 * cm)
+
         xcenter = self.pagesize[0] / 2.0
         if logo :
-            try :    
+            try :
                 imglogo = PIL.Image.open(logo)
-            except IOError :    
+            except IOError :
                 self.printInfo("Unable to open image %s" % logo, "warn")
             else :
                 (width, height) = imglogo.size
-                multi = float(width) / (8 * cm) 
+                multi = float(width) / (8 * cm)
                 width = float(width) / multi
                 height = float(height) / multi
@@ -140,5 +140,5 @@
                                   self.ypos, \
                                   width, height)
-        
+
         self.ypos -= (cm + 20)
         self.canvas.setFont("Helvetica-Bold", 14)
@@ -146,20 +146,20 @@
         msg = _("Here's the invoice for your printouts")
         self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
-        
+
         self.yorigine = self.ypos
         self.canvas.restoreState()
         self.canvas.endForm()
-        
-    def endPDF(self, fname) :    
+
+    def endPDF(self, fname) :
         """Flushes the PDF generator."""
         self.canvas.save()
-        if fname != "-" :        
+        if fname != "-" :
             outfile = open(fname, "w")
             outfile.write(self.pdfDocument.getvalue())
             outfile.close()
-        else :    
+        else :
             sys.stdout.write(self.pdfDocument.getvalue())
             sys.stdout.flush()
-        
+
     def genInvoices(self, peruser, logo, outfname, firstnumber, unit, vat) :
         """Generates the invoices file."""
@@ -168,5 +168,5 @@
             if outfname != "-" :
                 percent.display("%s...\n" % _("Generating invoices"))
-                
+
             self.initPDF(logo)
             number = firstnumber
@@ -175,11 +175,11 @@
                 if outfname != "-" :
                     percent.oneMore()
-                    
+
             if number > firstnumber :
                 self.endPDF(outfname)
-                
+
             if outfname != "-" :
                 percent.done()
-        
+
     def main(self, arguments, options) :
         """Generate invoices."""
@@ -188,9 +188,9 @@
         if not hasPIL :
             raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
-            
+
         self.adminOnly()
-        
+
         self.pagesize = getPageSize(options.pagesize)
-            
+
         extractonly = {}
         for filterexp in arguments :
@@ -200,33 +200,33 @@
                     filterkey = filterkey.lower()
                     if filterkey not in self.validfilterkeys :
-                        raise ValueError                
-                except ValueError :    
+                        raise ValueError
+                except ValueError :
                     raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
-                else :    
+                else :
                     extractonly.update({ filterkey : filtervalue })
-            
+
         percent = Percent(self)
         outfname = options.output.strip().encode(sys.getfilesystemencoding())
         if outfname != "-" :
             percent.display("%s..." % _("Extracting datas"))
-            
-        username = extractonly.get("username")    
+
+        username = extractonly.get("username")
         if username :
             user = self.storage.getUser(username)
         else :
             user = None
-            
-        printername = extractonly.get("printername")    
+
+        printername = extractonly.get("printername")
         if printername :
             printer = self.storage.getPrinter(printername)
-        else :    
+        else :
             printer = None
-            
+
         start = extractonly.get("start")
         end = extractonly.get("end")
         (start, end) = self.storage.cleanDates(start, end)
-        
-        jobs = self.storage.retrieveHistory(user=user,    
-                                            printer=printer, 
+
+        jobs = self.storage.retrieveHistory(user=user,
+                                            printer=printer,
                                             hostname=extractonly.get("hostname"),
                                             billingcode=extractonly.get("billingcode"),
@@ -235,13 +235,13 @@
                                             end=end,
                                             limit=0)
-            
-        peruser = {}                                    
-        nbjobs = 0                                    
-        nbpages = 0                                            
+
+        peruser = {}
+        nbjobs = 0
+        nbpages = 0
         nbcredits = 0.0
         percent.setSize(len(jobs))
         if outfname != "-" :
             percent.display("\n")
-        for job in jobs :                                    
+        for job in jobs :
             if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
                 nbpages += job.JobSize
@@ -256,16 +256,16 @@
         if outfname != "-" :
             percent.done()
-        self.genInvoices(peruser, 
-                         options.logo.strip().encode(sys.getfilesystemencoding()), 
-                         outfname, 
-                         options.number, 
-                         options.unit or _("Credits"), 
+        self.genInvoices(peruser,
+                         options.logo.strip().encode(sys.getfilesystemencoding()),
+                         outfname,
+                         options.number,
+                         options.unit or _("Credits"),
                          options.vat)
-        if outfname != "-" :    
+        if outfname != "-" :
             nbusers = len(peruser)
             print _("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
                      % locals()
-                     
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("Invoice generator for PyKota."),
                                 usage="pkinvoice [options] [filterexpr]")
@@ -281,5 +281,5 @@
                             default=u"A4",
                             help=_("Set the size of the page. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default page size is %default."))
-    parser.add_option("-n", "--number",                        
+    parser.add_option("-n", "--number",
                             dest="number",
                             type="int",
@@ -293,13 +293,13 @@
                             default=u"-",
                             help=_("The name of the file to which the PDF invoices will be written. If not set or set to '%default', the PDF document will be sent to the standard output."))
-                            
+
     # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861
     # TODO : we can't use 'default=_("Credits")' for this option
-    parser.add_option("-u", "--unit",                   
+    parser.add_option("-u", "--unit",
                             dest="unit",
                             type="string",
                             help=_("The name of the unit to use on the invoices. The default value is 'Credits' or its locale translation."))
-                            
-    parser.add_option("-V", "--vat", 
+
+    parser.add_option("-V", "--vat",
                             dest="vat",
                             type="float",
@@ -308,15 +308,15 @@
                             default=0.0,
                             help=_("The value in percent of the applicable VAT to be exposed. The default is %default, meaning no VAT."))
-                            
-    parser.add_filterexpression("username", _("User's name"))                        
-    parser.add_filterexpression("printername", _("Printer's name"))                        
-    parser.add_filterexpression("hostname", _("Host's name"))                        
-    parser.add_filterexpression("jobid", _("Job's id"))                        
+
+    parser.add_filterexpression("username", _("User's name"))
+    parser.add_filterexpression("printername", _("Printer's name"))
+    parser.add_filterexpression("hostname", _("Host's name"))
+    parser.add_filterexpression("jobid", _("Job's id"))
     parser.add_filterexpression("billingcode", _("Job's billing code"))
-    parser.add_filterexpression("start", _("Job's date of printing"))                        
-    parser.add_filterexpression("end", _("Job's date of printing"))                        
-    
-    parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', 
+    parser.add_filterexpression("start", _("Job's date of printing"))
+    parser.add_filterexpression("end", _("Job's date of printing"))
+
+    parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30',
                        _("This would generate a PDF document containing invoices for all users who have spent some credits last month. Amounts would be in EURO and not VAT information would be included."))
-                       
-    run(parser, PKInvoice)                   
+
+    run(parser, PKInvoice)
Index: pykota/trunk/bin/pykotme
===================================================================
--- pykota/trunk/bin/pykotme (revision 3411)
+++ pykota/trunk/bin/pykotme (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/>.
@@ -34,6 +34,6 @@
 from pykota.tool import PyKotaTool
 from pykota.accounter import openAccounter
-    
-class PyKotMe(PyKotaTool) :        
+
+class PyKotMe(PyKotaTool) :
     """A class for pykotme."""
     def main(self, files, options) :
@@ -46,29 +46,29 @@
             # TODO : over the second printer below.
             files.append("-")
-            
+
         printers = self.storage.getMatchingPrinters(options.printer)
         if not printers :
             raise PyKotaCommandLineError, _("There's no printer matching %s") % options.printer
-            
+
         username = pwd.getpwuid(os.getuid())[0]
         if options.user :
             if not self.config.isAdmin :
                 self.printInfo(_("The --user command line option will be ignored because you are not a PyKota Administrator."), "warn")
-            else :    
+            else :
                 username = options.user
-            
+
         user = self.storage.getUser(username)
         if not user.Exists :
             self.printInfo(_("There's no user matching '%(username)s'.") \
                                               % locals(),
-                           "error")                   
-        else :                   
+                           "error")
+        else :
             if user.LimitBy and (user.LimitBy.lower() == "balance"):
                 self.display("%s\n" % (_("Your account balance : %.2f") % (user.AccountBalance or 0.0)))
-            
+
             sizeprinted = False
             done = {}
             for printer in printers :
-                # Now fake some values. TODO : improve API to not need this anymore    
+                # Now fake some values. TODO : improve API to not need this anymore
                 printername = printer.Name
                 self.PrinterName = printer.Name
@@ -77,7 +77,7 @@
                 key = self.preaccounter.name + self.preaccounter.arguments
                 if not done.has_key(key) :
-                    totalsize = 0    
+                    totalsize = 0
                     inkusage = []
-                    for filename in files :    
+                    for filename in files :
                         self.DataFile = filename
                         self.preaccounter.beginJob(None)
@@ -85,7 +85,7 @@
                         totalsize += self.preaccounter.getJobSize(None)
                         inkusage.extend(self.preaccounter.inkUsage)
-                    done[key] = (totalsize, inkusage)    
-                (totalsize, inkusage) = done[key]    
-                if not sizeprinted :    
+                    done[key] = (totalsize, inkusage)
+                (totalsize, inkusage) = done[key]
+                if not sizeprinted :
                     self.display("%s\n" % (_("Job size : %i pages") % totalsize))
                     sizeprinted = True
@@ -94,16 +94,16 @@
                     if printer.MaxJobSize and (totalsize > printer.MaxJobSize) :
                         self.display("%s\n" % (_("User %(username)s is not allowed to print so many pages on printer %(printername)s at this time.") % locals()))
-                    else :    
+                    else :
                         cost = userpquota.computeJobPrice(totalsize, inkusage)
                         msg = _("Cost on printer %s : %.2f") % (printer.Name, cost)
                         if printer.PassThrough :
                             msg = "%s (%s)" % (msg, _("won't be charged, printer is in passthrough mode"))
-                        elif user.LimitBy == "nochange" :    
+                        elif user.LimitBy == "nochange" :
                             msg = "%s (%s)" % (msg, _("won't be charged, account is immutable"))
                         self.display("%s\n" % msg)
             if user.LimitBy == "noprint" :
                 self.display("%s\n" % (_("User %(username)s is forbidden to print at this time.") % locals()))
-            
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("Generates print quotes for end users."),
                                 usage="pykotme [options] [files]")
@@ -116,5 +116,5 @@
                             help=_("Acts on this user only. Only one username can be specified this way. The default value is the name of the user who launched this command. This option is ignored when the command is not launched by a PyKota Administrator."))
 
-    parser.add_example("--printer apple file1.ps <file2.pclxl",                            
+    parser.add_example("--printer apple file1.ps <file2.pclxl",
                        _("This would show the number of pages needed to print these two files, as well as the cost of printing them to the 'apple' printer for the user who launched this command."))
     parser.add_example("--user john",
Index: pykota/trunk/bin/pkbcodes
===================================================================
--- pykota/trunk/bin/pkbcodes (revision 3411)
+++ pykota/trunk/bin/pkbcodes (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/>.
@@ -35,30 +35,30 @@
 from pykota.storage import StorageBillingCode
 
-class PKBcodes(PyKotaTool) :        
+class PKBcodes(PyKotaTool) :
     """A class for a billing codes manager."""
     def modifyBillingCode(self, billingcode, reset, description) :
         """Modifies a billing code."""
         if reset :
-            billingcode.reset()    
+            billingcode.reset()
         if description is not None : # NB : "" is allowed !
             billingcode.setDescription(description)
-        
+
     def main(self, names, options) :
         """Manage billing codes."""
         if options.action != "list" :
             self.adminOnly()
-           
+
         islist = (options.action == "list")
         isadd = (options.action == "add")
         isdelete = (options.action == "delete")
-        
+
         if not names :
             if isdelete or isadd :
                 raise PyKotaCommandLineError, _("You must specify billing codes on the command line.")
-            names = [u"*"]    
-            
+            names = [u"*"]
+
         if not islist :
             percent = Percent(self)
-            
+
         if not isadd :
             if not islist :
@@ -73,5 +73,5 @@
             if not islist :
                 percent.setSize(len(billingcodes))
-                        
+
         if islist :
             for billingcode in billingcodes :
@@ -83,5 +83,5 @@
                        billingcode.Balance, \
                        _("credits")))
-        elif isdelete :    
+        elif isdelete :
             percent.display("\n%s..." % _("Deletion"))
             self.storage.deleteManyBillingCodes(billingcodes)
@@ -91,14 +91,14 @@
             if description :
                 description = description.strip()
-            
+
             self.storage.beginTransaction()
             try :
-                if isadd :    
+                if isadd :
                     percent.display("%s...\n" % _("Creation"))
                     percent.setSize(len(names))
                     for bname in names :
                         billingcode = StorageBillingCode(self.storage, bname)
-                        self.modifyBillingCode(billingcode, 
-                                               options.reset, 
+                        self.modifyBillingCode(billingcode,
+                                               options.reset,
                                                description)
                         oldbillingcode = self.storage.addBillingCode(billingcode)
@@ -106,29 +106,29 @@
                             if options.skipexisting :
                                 self.logdebug(_("Billing code '%(bname)s' already exists, skipping.") % locals())
-                            else :    
+                            else :
                                 self.logdebug(_("Billing code '%(bname)s' already exists, will be modified.") % locals())
-                                self.modifyBillingCode(oldbillingcode, 
-                                                       options.reset, 
+                                self.modifyBillingCode(oldbillingcode,
+                                                       options.reset,
                                                        description)
                                 oldbillingcode.save()
                         percent.oneMore()
-                else :        
+                else :
                     percent.display("\n%s...\n" % _("Modification"))
                     for billingcode in billingcodes :
-                        self.modifyBillingCode(billingcode, 
-                                               options.reset, 
+                        self.modifyBillingCode(billingcode,
+                                               options.reset,
                                                description)
-                        billingcode.save()    
+                        billingcode.save()
                         percent.oneMore()
-            except :                    
+            except :
                 self.storage.rollbackTransaction()
                 raise
-            else :    
+            else :
                 self.storage.commitTransaction()
-                        
+
         if not islist :
             percent.done()
-                     
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("A billing codes manager for PyKota."),
                                 usage="pkbcodes [options] code1 code2 ... codeN")
@@ -159,12 +159,12 @@
                             dest="skipexisting",
                             help=_("If --add is used, ensure that existing billing codes won't be modified."))
-                            
+
     parser.add_example('-D "Financial Department" financial',
                        _("Would create a billing code labelled 'financial' with the specified textual description."))
-    parser.add_example('--delete "fin*"', 
+    parser.add_example('--delete "fin*"',
                        _("Would delete all billing codes which label begins with 'fin'. Matching jobs in the printing history wouldn't be deleted though."))
     parser.add_example("--list",
                        _("Would display details about all existing billing codes."))
-                       
+
     (options, arguments) = parser.parse_args()
-    run(parser, PKBcodes)                   
+    run(parser, PKBcodes)
Index: pykota/trunk/bin/pksetup
===================================================================
--- pykota/trunk/bin/pksetup (revision 3411)
+++ pykota/trunk/bin/pksetup (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/>.
@@ -60,5 +60,5 @@
 # IMPORTANT : many more directives can be used, and some of the directives
 # below accept different and/or more complex parameters. Please read
-# /usr/share/pykota/conf/pykota.conf.sample for more details about the 
+# /usr/share/pykota/conf/pykota.conf.sample for more details about the
 # numerous possibilities allowed.
 #
@@ -122,5 +122,5 @@
 onaccountererror : stop
 
-# Who will receive warning messages ? 
+# Who will receive warning messages ?
 # both means admin and user.
 mailto : both
@@ -136,11 +136,11 @@
 poorman : 1.0
 
-# Warning messages to use 
-poorwarn : Your Print Quota account balance is low. 
+# Warning messages to use
+poorwarn : Your Print Quota account balance is low.
  Soon you'll not be allowed to print anymore.
 
 softwarn : Your Print Quota Soft Limit is reached.
  This means that you may still be allowed to print for some
- time, but you must contact your administrator to purchase 
+ time, but you must contact your administrator to purchase
  more print quota.
 
@@ -150,5 +150,5 @@
  as soon as possible to solve the problem.
 
-# Number of banners allowed to be printed by users 
+# Number of banners allowed to be printed by users
 # who are over quota
 maxdenybanners : 0
@@ -181,8 +181,8 @@
     cupsrestart = "/etc/init.d/cupsys restart"  # overload it if needed
     adduser = "adduser --system --group --home /etc/pykota --gecos PyKota pykota" # overload it if needed
-    packages = [ "wget", 
-                 "bzip2", 
-                 "subversion", 
-                 "postgresql", 
+    packages = [ "wget",
+                 "bzip2",
+                 "subversion",
+                 "postgresql",
                  "postgresql-client",
                  "cupsys",
@@ -200,5 +200,5 @@
                  "python-pam",
                  "pkpgcounter" ]
-        
+
     otherpackages = [
                       { "name" : "pkipplib",
@@ -219,18 +219,18 @@
                                      ],
                       },
-                   ]  
-                   
-    def __init__(self) :               
+                   ]
+
+    def __init__(self) :
         """Initializes instance specific datas."""
         self.launched = []
-        
-    def yesno(self, message) :        
+
+    def yesno(self, message) :
         """Asks the end user some question and returns the answer."""
         try :
             return raw_input("\n%s ? " % message).strip().upper()[0] == 'Y'
-        except IndexError :    
+        except IndexError :
             return False
-        
-    def confirmCommand(self, message, command, record=True) :    
+
+    def confirmCommand(self, message, command, record=True) :
         """Asks for confirmation before a command is launched, and launches it if needed."""
         if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) :
@@ -241,6 +241,6 @@
         else :
             return False
-            
-    def confirmPipe(self, message, command) :    
+
+    def confirmPipe(self, message, command) :
         """Asks for confirmation before a command is launched in a pipe, launches it if needed, and returns the result."""
         if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) :
@@ -251,5 +251,5 @@
         else :
             return False
-            
+
     def listPrinters(self) :
         """Returns a list of tuples (queuename, deviceuri) for all existing print queues."""
@@ -263,7 +263,7 @@
             queuename = begin.split()[-1]
             printers.append((queuename, deviceuri))
-        return printers    
-        
-    def downloadOtherPackages(self) :    
+        return printers
+
+    def downloadOtherPackages(self) :
         """Downloads and install additional packages from http://www.pykota.com or other websites"""
         olddirectory = os.getcwd()
@@ -278,23 +278,23 @@
             if url.startswith("svn://") :
                 download = 'svn export "%(url)s" %(name)s' % locals()
-            else :    
+            else :
                 download = 'wget --user-agent=pksetup "%(url)s"' % locals()
             if self.confirmCommand("to download %(name)s" % locals(), download) :
                 self.confirmCommand("to install %(name)s" % locals(), commands)
-        self.confirmCommand("to remove the temporary directory %(directory)s" % locals(),     
+        self.confirmCommand("to remove the temporary directory %(directory)s" % locals(),
                             "rm -fr %(directory)s" % locals(),
                             record=False)
-        os.chdir(olddirectory)    
-        
+        os.chdir(olddirectory)
+
     def waitPrintersOnline(self) :
         """Asks the admin to switch all printers ON."""
         while not self.yesno("First you MUST switch ALL your printers ON. Are ALL your printers ON") :
             pass
-            
+
     def setupDatabase(self) :
         """Creates the database."""
         pykotadirectory = self.pykotadirectory
         self.confirmCommand("to create PyKota's database in PostgreSQL", 'su - postgres -c "psql -f %(pykotadirectory)s/postgresql/pykota-postgresql.sql template1"' % locals())
-        
+
     def configurePostgreSQL(self) :
         """Configures PostgreSQL for PyKota to work."""
@@ -314,7 +314,7 @@
                         if tcpip is False :
                             tcpip = answer.startswith("listen_addresses")
-                    else :    
+                    else :
                         tcpip = False
-                    if tcpip :    
+                    if tcpip :
                         conflines.insert(2, "host\tpykota\tpykotaadmin,pykotauser\t127.0.0.1\t255.255.255.255\tmd5")
                     else :
@@ -333,10 +333,10 @@
                     return (tcpip, port)
         return (None, None)
-        
+
     def genConfig(self, adminname, adminemail, dnsdomain, smtpserver, home, tcpip, port) :
         """Generates minimal configuration files for PyKota."""
         if tcpip :
             storageserver = "localhost:%i" % port
-        else :    
+        else :
             storageserver = ""
         conf = pykotaconf % locals()
@@ -356,26 +356,26 @@
                     if begin is None :
                         begin = i
-                    else :    
+                    else :
                         end = i
-                        
+
             if (begin is not None) and (end is not None) :
                 suffix = "\n".join(lines[begin+1:end])
                 self.confirmCommand("to improve PyKota's configuration wrt your existing printers", 'echo "%(suffix)s" >>%(home)s/pykota.conf' % locals())
-        
+
     def addPyKotaUser(self) :
         """Adds a system user named pykota, returns its home directory or None"""
         try :
             user = pwd.getpwnam("pykota")
-        except KeyError :    
+        except KeyError :
             if self.confirmCommand("to create a system user named 'pykota'", self.adduser) :
-                try :    
+                try :
                     return pwd.getpwnam("pykota")[5]
-                except KeyError :    
+                except KeyError :
                     return None
-            else :        
+            else :
                 return None
-        else :    
+        else :
             return user[5]
-    
+
     def setupBackend(self) :
         """Installs the cupspykota backend."""
@@ -385,11 +385,11 @@
             self.confirmCommand("to make PyKota known to CUPS", "ln -s %(realbackend)s %(backend)s" % locals())
             self.confirmCommand("to restart CUPS for the changes to take effect", self.cupsrestart)
-        
-    def managePrinters(self, printers) :    
+
+    def managePrinters(self, printers) :
         """For each printer, asks if it should be managed with PyKota or not."""
         for (queuename, deviceuri) in printers :
             command = 'pkprinters --add --cups --description "Printer created with pksetup" "%(queuename)s"' % locals()
             self.confirmCommand("to import the %(queuename)s print queue into PyKota's database and reroute it through PyKota" % locals(), command)
-    
+
     def installPyKotaFiles(self) :
         """Installs PyKota files through Python's Distutils mechanism."""
@@ -399,5 +399,5 @@
         if os.path.exists(setuppy) :
             self.confirmCommand("to install PyKota files on your system", "python %(setuppy)s install" % locals())
-        
+
     def setup(self) :
         """Installation procedure."""
@@ -411,5 +411,5 @@
         if homedirectory is None :
             logerr("Installation can't proceed. You MUST create a system user named 'pykota'.\n")
-        else :    
+        else :
             self.upgradeSystem()
             self.setupPackages()
@@ -422,55 +422,55 @@
             print nowready
             print "The script %s can be used to reinstall in unattended mode.\n" % self.genInstaller()
-            
-    def genInstaller(self) :        
+
+    def genInstaller(self) :
         """Generates an installer script."""
         scriptname = "/tmp/pykota-installer.sh"
-        commands = [ "#! /bin/sh", 
+        commands = [ "#! /bin/sh",
                      "#",
-                     "# PyKota installer script.", 
-                     "#", 
+                     "# PyKota installer script.",
+                     "#",
                      "# This script was automatically generated.",
                      "#",
                    ] + self.launched
-        script = open(scriptname, "w")           
+        script = open(scriptname, "w")
         script.write("\n".join(commands))
         script.close()
         os.chmod(scriptname, \
                  stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
-        return scriptname    
-        
-        
-class Debian(PyKotaSetup) :        
+        return scriptname
+
+
+class Debian(PyKotaSetup) :
     """Class for Debian installer."""
-    def setupPackages(self) :    
+    def setupPackages(self) :
         """Installs missing Debian packages."""
         self.confirmCommand("to install missing dependencies", "apt-get install %s" % " ".join(self.packages))
-            
+
     def upgradeSystem(self) :
         """Upgrades the Debian setup."""
         if self.confirmCommand("to grab an up-to-date list of available packages", "apt-get update") :
             self.confirmCommand("to put your system up-to-date", "apt-get -y dist-upgrade")
-    
-class Ubuntu(Debian) :    
+
+class Ubuntu(Debian) :
     """Class for Ubuntu installer."""
     pass
-    
-if __name__ == "__main__" :        
+
+if __name__ == "__main__" :
     retcode = 0
     if (len(sys.argv) != 2) or (sys.argv[1] == "-h") or (sys.argv[1] == "--help") :
         print "pksetup v0.1 (c) 2003-2008 Jerome Alet - alet@librelogiciel.com\n\nusage : pksetup distribution\n\ne.g. : pksetup debian\n\nIMPORTANT : only Debian and Ubuntu are currently supported."
-    elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") :    
+    elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") :
         print "0.1" # pksetup's own version number
-    else :    
+    else :
         classname = sys.argv[1].strip().title()
         try :
             installer = globals()[classname]()
-        except KeyError :    
+        except KeyError :
             logerr("There's currently no support for the %s distribution, sorry.\n" % sys.argv[1])
             retcode = -1
-        else :    
+        else :
             try :
                 retcode = installer.setup()
-            except KeyboardInterrupt :             
+            except KeyboardInterrupt :
                 logerr("\n\n\nWARNING : Setup was aborted at user's request !\n\n")
                 retcode = -1
Index: pykota/trunk/bin/pkusers
===================================================================
--- pykota/trunk/bin/pkusers (revision 3411)
+++ pykota/trunk/bin/pkusers (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/>.
@@ -40,6 +40,6 @@
 
   pkusers [options] user1 user2 user3 ... userN
-  
-or :  
+
+or :
 
   pkusers --groups [options] group1 group2 group3 ... groupN
@@ -49,9 +49,9 @@
   -v | --version       Prints pkusers's version number then exits.
   -h | --help          Prints this message then exits.
-  
+
   -a | --add           Adds users if they don't exist on the database.
                        If they exist, they are modified unless
                        -s|--skipexisting is also used.
-                       
+
   -d | --delete        Deletes users from the quota storage.
 
@@ -62,9 +62,9 @@
 
   -D | --description d Adds a textual description to users or groups.
-                       
+
   -g | --groups        Edit users groups instead of users.
-                          
-  -o | --overcharge f  Sets the overcharging factor applied to the user 
-                       when computing the cost of a print job. Positive or 
+
+  -o | --overcharge f  Sets the overcharging factor applied to the user
+                       when computing the cost of a print job. Positive or
                        negative floating point values are allowed,
                        this allows you to do some really creative
@@ -74,25 +74,25 @@
                        cost of the job for a particular user.
                        Only users have such a coefficient.
-  
+
   -i | --ingroups g1[,g2...]  Puts the users into each of the groups
                               listed, separated by commas. The groups
                               must already exist in the Quota Storage.
-                       
+
   -L | --list          Lists users or groups.
-  
-  -l | --limitby l     Choose if the user/group is limited in printing                     
+
+  -l | --limitby l     Choose if the user/group is limited in printing
                        by its account balance or by its page quota.
                        The default value is 'quota'. Allowed values
-                       are 'quota' 'balance' 'noquota' 'noprint' 
+                       are 'quota' 'balance' 'noquota' 'noprint'
                        and 'nochange' :
-                       
+
                          - quota : limit by number of pages per printer.
                          - balance : limit by number of credits in account.
                          - noquota : no limit, accounting still done.
-                         - nochange : no limit, accounting not done. 
-                         - noprint : printing is denied. 
+                         - nochange : no limit, accounting not done.
+                         - noprint : printing is denied.
                        NB : nochange and noprint are not supported for groups.
-                       
-  -b | --balance b     Sets the user's account balance to b.                     
+
+  -b | --balance b     Sets the user's account balance to b.
                        Account balance may be increase or decreased
                        if b is prefixed with + or -.
@@ -102,59 +102,59 @@
                        Groups don't have a real balance, but the
                        sum of their users' account balance.
-                       
+
   -C | --comment txt   Defines some informational text to be associated
                        with a change to an user's account balance.
                        Only meaningful if -b | --balance is also used.
-                       
-                       
-  -r | --remove        In combination with the --ingroups option above,                       
+
+
+  -r | --remove        In combination with the --ingroups option above,
                        remove users from the specified users groups.
-                       
+
   -s | --skipexisting  In combination with the --add option above, tells
                        pkusers to not modify existing users.
-                       
+
   user1 through userN and group1 through groupN can use wildcards
   if the --add option is not set.
-  
-examples :                              
+
+examples :
 
   $ pkusers --add john paul george ringo/ringo@example.com
-  
+
   This will add users john, paul, george and ringo to the quota
-  database. User ringo's email address will also be set to 
+  database. User ringo's email address will also be set to
   'ringo@example.com'
-  
+
   $ pkusers --ingroups coders,it jerome
-  
+
   User jerome is put into the groups "coders" and "it" which must
   already exist in the quota database.
-            
+
   $ pkusers --limitby balance jerome
-  
+
   This will tell PyKota to limit jerome by his account's balance
   when printing.
-  
+
   $ pkusers --balance +10.0 --comment "He paid with his blood !" jerome
-  
+
   This will increase jerome's account balance by 10.0 (in your
   own currency). You can decrease the account balance with a
   dash prefix, and set it to a fixed amount with no prefix.
   A comment will be stored for this balance change.
-  
+
   $ pkusers --delete jerome rachel
-  
+
   This will completely delete jerome and rachel from the quota
   database. All their quotas and jobs will be deleted too.
-  
+
   $ pkusers --overcharge 2.5 poorstudent
-  
+
   This will overcharge the poorstudent user by a factor of 2.5.
-  
+
   $ pkusers --overcharge -1 jerome
-  
+
   User jerome will actually earn money whenever he prints.
-  
+
   $ pkusers --overcharge 0 boss
-  
+
   User boss can print at will, it won't cost him anything because the
   cost of each print job will be multiplied by zero before charging
@@ -165,6 +165,6 @@
   This will set the email address for each user to username@example.com
 """)
-        
-class PKUsers(PyKotaTool) :        
+
+class PKUsers(PyKotaTool) :
     """A class for a users and users groups manager."""
     def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) :
@@ -172,5 +172,5 @@
         if description is not None : # NB : "" is allowed !
             entry.setDescription(description)
-        if limitby :    
+        if limitby :
             entry.setLimitBy(limitby)
         if not groups :
@@ -181,5 +181,5 @@
                     raise PyKotaCommandLineError, _("Invalid email address %s") % email
                 entry.setEmail(email)
-            if overcharge is not None : # NB : 0 is allowed !     
+            if overcharge is not None : # NB : 0 is allowed !
                 entry.setOverChargeFactor(overcharge)
             if balance :
@@ -192,6 +192,6 @@
                     newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
                     entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
-                    
-    def manageUsersGroups(self, ugroups, user, remove) :        
+
+    def manageUsersGroups(self, ugroups, user, remove) :
         """Manage user group membership."""
         for ugroup in ugroups :
@@ -200,13 +200,13 @@
             else :
                 ugroup.addUserToGroup(user)
-                
+
     def main(self, names, options) :
         """Manage users or groups."""
         names = self.sanitizeNames(options, names)
-        suffix = (options["groups"] and "Group") or "User"        
-        
+        suffix = (options["groups"] and "Group") or "User"
+
         if not options["list"] :
             percent = Percent(self)
-            
+
         if not options["add"] :
             if not options["list"] :
@@ -219,7 +219,7 @@
                     percent.display("\n")
                 raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names))
-            if not options["list"] :    
+            if not options["list"] :
                 percent.setSize(len(entries))
-                
+
         if options["list"] :
             if suffix == "User" :
@@ -229,14 +229,14 @@
                     email = entry.Email
                     if not email :
-                        if maildomain :     
+                        if maildomain :
                             email = "%s@%s" % (entry.Name, maildomain)
-                        elif smtpserver :    
+                        elif smtpserver :
                             email = "%s@%s" % (entry.Name, smtpserver)
-                        else :    
+                        else :
                             email = "%s@%s" % (entry.Name, "localhost")
                     msg = "%s - <%s>" % (entry.Name, email)
                     if entry.Description :
                         msg += " - %s" % entry.Description
-                    print msg    
+                    print msg
                     print "    %s" % (_("Limited by : %s") % entry.LimitBy)
                     print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
@@ -244,15 +244,15 @@
                     print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
                     print
-            else :    
+            else :
                 for entry in entries :
                     msg = "%s" % entry.Name
                     if entry.Description :
                         msg += " - %s" % entry.Description
-                    print msg    
+                    print msg
                     print "    %s" % (_("Limited by : %s") % entry.LimitBy)
                     print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
                     print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
                     print
-        elif options["delete"] :    
+        elif options["delete"] :
             percent.display("\n%s..." % _("Deletion"))
             getattr(self.storage, "deleteMany%ss" % suffix)(entries)
@@ -266,14 +266,14 @@
                                             'noprint', 'nochange') :
                     raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
-                if (limitby in ('nochange', 'noprint')) and options["groups"] :    
+                if (limitby in ('nochange', 'noprint')) and options["groups"] :
                     raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
-                
+
             overcharge = options["overcharge"]
             if overcharge :
                 try :
                     overcharge = float(overcharge.strip())
-                except (ValueError, AttributeError) :    
+                except (ValueError, AttributeError) :
                     raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
-                    
+
             balance = options["balance"]
             if balance :
@@ -281,33 +281,33 @@
                 try :
                     balancevalue = float(balance)
-                except ValueError :    
+                except ValueError :
                     raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
-            else :    
+            else :
                 balancevalue = None
-                
+
             if options["ingroups"] :
                 usersgroups = self.storage.getMatchingGroups(options["ingroups"])
                 if not usersgroups :
                     raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
-            else :         
+            else :
                 usersgroups = []
-                    
+
             description = options["description"]
             if description :
                 description = description.strip()
-                
+
             comment = options["comment"]
             if comment :
                 comment = comment.strip()
-            email = options["email"]    
+            email = options["email"]
             if email :
                 email = email.strip()
-            skipexisting = options["skipexisting"]    
+            skipexisting = options["skipexisting"]
             groups = options["groups"]
             remove = options["remove"]
             self.storage.beginTransaction()
-            try :    
-                if options["add"] :    
-                    rejectunknown = self.config.getRejectUnknown()    
+            try :
+                if options["add"] :
+                    rejectunknown = self.config.getRejectUnknown()
                     percent.display("%s...\n" % _("Creation"))
                     percent.setSize(len(names))
@@ -325,19 +325,19 @@
                                     try :
                                         grp.getgrnam(ename)
-                                    except KeyError :    
+                                    except KeyError :
                                         self.printInfo(_("Unknown group %s") % ename, "error")
                                         reject = 1
-                                else :    
+                                else :
                                     try :
                                         pwd.getpwnam(ename)
-                                    except KeyError :    
+                                    except KeyError :
                                         self.printInfo(_("Unknown user %s") % ename, "error")
                                         reject = 1
-                            if not reject :        
+                            if not reject :
                                 entry = globals()["Storage%s" % suffix](self.storage, ename)
                                 if groups :
                                     self.modifyEntry(entry, groups, limitby, \
                                                      description)
-                                else :    
+                                else :
                                     self.modifyEntry(entry, groups, limitby, \
                                                      description, overcharge,\
@@ -348,5 +348,5 @@
                                     if skipexisting :
                                         self.logdebug(_("%s %s already exists, skipping.") % (_(suffix), ename))
-                                    else :    
+                                    else :
                                         self.logdebug(_("%s %s already exists, will be modified.") % (_(suffix), ename))
                                         if groups :
@@ -373,21 +373,21 @@
                         if groups :
                             self.modifyEntry(entry, groups, limitby, description)
-                        else :    
+                        else :
                             self.modifyEntry(entry, groups, limitby, description, \
                                              overcharge, balance, balancevalue, \
                                              comment, email)
-                            self.manageUsersGroups(usersgroups, entry, remove)                
-                        entry.save()    
+                            self.manageUsersGroups(usersgroups, entry, remove)
+                        entry.save()
                         percent.oneMore()
-            except :                    
+            except :
                 self.storage.rollbackTransaction()
                 raise
-            else :    
+            else :
                 self.storage.commitTransaction()
-                
+
         if not options["list"] :
             percent.done()
-                     
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     retcode = 0
     try :
@@ -401,13 +401,13 @@
                         "ingroups=", "limitby=", "balance=", "comment=", \
                        ]
-                        
-        
+
+
         # Initializes the command line tool
         manager = PKUsers(doc=__doc__)
         manager.deferredInit()
-        
+
         # parse and checks the command line
         (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
-        
+
         # sets long options
         options["help"] = options["h"] or options["help"]
@@ -415,5 +415,5 @@
         options["add"] = options["a"] or options["add"]
         options["description"] = options["D"] or options["description"]
-        options["delete"] = options["d"] or options["delete"] 
+        options["delete"] = options["d"] or options["delete"]
         options["groups"] = options["g"] or options["groups"]
         options["list"] = options["L"] or options["list"]
@@ -421,10 +421,10 @@
         options["skipexisting"] = options["s"] or options["skipexisting"]
         options["limitby"] = options["l"] or options["limitby"]
-        options["balance"] = options["b"] or options["balance"] 
+        options["balance"] = options["b"] or options["balance"]
         options["ingroups"] = options["i"] or options["ingroups"]
         options["overcharge"] = options["o"] or options["overcharge"]
         options["comment"] = options["C"] or options["comment"] or defaults["comment"]
         options["email"] = options["e"] or options["email"]
-        
+
         if options["help"] :
             manager.display_usage_and_quit()
@@ -436,5 +436,5 @@
            or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
             raise PyKotaCommandLineError, _("incompatible options, see help.")
-        elif options["remove"] and not options["ingroups"] :    
+        elif options["remove"] and not options["ingroups"] :
             raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
         elif (not args) and (options["add"] or options["delete"]) :
@@ -442,16 +442,16 @@
         else :
             retcode = manager.main(args, options)
-    except KeyboardInterrupt :        
+    except KeyboardInterrupt :
         logerr("\nInterrupted with Ctrl+C !\n")
         retcode = -3
-    except PyKotaCommandLineError, msg :    
+    except PyKotaCommandLineError, msg :
         logerr("%s : %s\n" % (sys.argv[0], msg))
         retcode = -2
-    except SystemExit :        
+    except SystemExit :
         pass
     except :
         try :
             manager.crashed("pkusers failed")
-        except :    
+        except :
             crashed("pkusers failed")
         retcode = -1
@@ -459,6 +459,6 @@
     try :
         manager.storage.close()
-    except (TypeError, NameError, AttributeError) :    
+    except (TypeError, NameError, AttributeError) :
         pass
-        
-    sys.exit(retcode)    
+
+    sys.exit(retcode)
Index: pykota/trunk/bin/pkturnkey
===================================================================
--- pykota/trunk/bin/pkturnkey (revision 3411)
+++ pykota/trunk/bin/pkturnkey (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,17 +51,17 @@
   -v | --version       Prints pkturnkey version number then exits.
   -h | --help          Prints this message then exits.
-  
+
   -c | --doconf        Give hints about what to put into pykota.conf
-  
+
   -d | --dousers       Manages users accounts as well.
-  
+
   -D | --dogroups      Manages users groups as well.
                        Implies -d | --dousers.
-  
+
   -e | --emptygroups   Includes empty groups.
-  
+
   -f | --force         Modifies the database instead of printing what
                        it would do.
-                       
+
   -u | --uidmin uid    Only adds users whose uid is greater than or equal to
                        uid. You can pass an username there as well, and its
@@ -69,5 +69,5 @@
                        If not set, 0 will be used automatically.
                        Implies -d | --dousers.
-                       
+
   -U | --uidmax uid    Only adds users whose uid is lesser than or equal to
                        uid. You can pass an username there as well, and its
@@ -81,5 +81,5 @@
                        If not set, 0 will be used automatically.
                        Implies -D | --dogroups.
-                       
+
   -G | --gidmax gid    Only adds groups whose gid is lesser than or equal to
                        gid. You can pass a groupname there as well, and its
@@ -88,5 +88,5 @@
                        Implies -D | --dogroups.
 
-examples :                              
+examples :
 
   $ pkturnkey --dousers --uidmin jerome
@@ -95,12 +95,12 @@
   printers and print accounts for all users whose uid is greater than
   or equal to jerome's one. Won't manage any users group.
-  
+
   To REALLY initialize the database instead of simulating it, please
   use the -f | --force command line switch.
-  
+
   You can limit the initialization to only a subset of the existing
   printers, by passing their names at the end of the command line.
 """)
-        
+
 class PKTurnKey(Tool) :
     """A class for an initialization tool."""
@@ -118,13 +118,13 @@
             if self.matchString(queuename, namestomatch) :
                 printers.append((queuename, deviceuri))
-            else :    
+            else :
                 self.printInfo("Print queue %s skipped." % queuename)
-        return printers    
-        
-    def listUsers(self, uidmin, uidmax) :    
+        return printers
+
+    def listUsers(self, uidmin, uidmax) :
         """Returns a list of users whose uids are between uidmin and uidmax."""
         self.printInfo("Extracting all users whose uid is between %s and %s." % (uidmin, uidmax))
         return [(entry[0], entry[3]) for entry in pwd.getpwall() if uidmin <= entry[2] <= uidmax]
-        
+
     def listGroups(self, gidmin, gidmax, users) :
         """Returns a list of groups whose gids are between gidmin and gidmax."""
@@ -135,7 +135,7 @@
         for u in users :
             gidusers.setdefault(u[1], []).append(u[0])
-            usersgid.setdefault(u[0], []).append(u[1]) 
-            
-        membership = {}    
+            usersgid.setdefault(u[0], []).append(u[1])
+
+        membership = {}
         for g in range(len(groups)) :
             (gname, gid, members) = groups[g]
@@ -145,23 +145,23 @@
             try :
                 usernames = gidusers[gid]
-            except KeyError :    
+            except KeyError :
                 pass
-            else :    
+            else :
                 for username in usernames :
                     if not newmembers.has_key(username) :
                         newmembers[username] = username
-            for member in newmembers.keys() :            
+            for member in newmembers.keys() :
                 if not usersgid.has_key(member) :
                     del newmembers[member]
             membership[gname] = newmembers.keys()
         return membership
-        
-    def runCommand(self, command, dryrun) :    
+
+    def runCommand(self, command, dryrun) :
         """Launches an external command."""
         self.printInfo("%s" % command)
-        if not dryrun :    
+        if not dryrun :
             os.system(command)
-            
-    def createPrinters(self, printers, dryrun=0) :    
+
+    def createPrinters(self, printers, dryrun=0) :
         """Creates all printers in PyKota's database."""
         if printers :
@@ -171,5 +171,5 @@
             args.close()
             self.runCommand("pkprinters --arguments /tmp/pkprinters.args", dryrun)
-        
+
     def createUsers(self, users, printers, dryrun=0) :
         """Creates all users in PyKota's database."""
@@ -180,5 +180,5 @@
             args.close()
             self.runCommand("pkusers --arguments /tmp/pkusers.users.args", dryrun)
-            
+
             printersnames = [p[0] for p in printers]
             args = open("/tmp/edpykota.users.args", "w")
@@ -188,5 +188,5 @@
             args.close()
             self.runCommand("edpykota --arguments /tmp/edpykota.users.args", dryrun)
-            
+
     def createGroups(self, groups, printers, dryrun=0) :
         """Creates all groups in PyKota's database."""
@@ -197,5 +197,5 @@
             args.close()
             self.runCommand("pkusers --arguments /tmp/pkusers.groups.args", dryrun)
-            
+
             printersnames = [p[0] for p in printers]
             args = open("/tmp/edpykota.groups.args", "w")
@@ -205,16 +205,16 @@
             args.close()
             self.runCommand("edpykota --arguments /tmp/edpykota.groups.args", dryrun)
-            
+
             revmembership = {}
             for (groupname, usernames) in groups.items() :
                 for username in usernames :
                     revmembership.setdefault(username, []).append(groupname)
-            commands = []        
-            for (username, groupnames) in revmembership.items() :        
+            commands = []
+            for (username, groupnames) in revmembership.items() :
                 commands.append('pkusers --ingroups %s "%s"' \
                     % (",".join(['"%s"' % g for g in groupnames]), username))
             for command in commands :
                 self.runCommand(command, dryrun)
-        
+
     def supportsSNMP(self, hostname, community) :
         """Returns 1 if the printer accepts SNMP queries, else 0."""
@@ -222,5 +222,5 @@
         try :
             from pysnmp.entity.rfc3413.oneliner import cmdgen
-        except ImportError :    
+        except ImportError :
             hasV4 = False
             try :
@@ -228,11 +228,11 @@
                 from pysnmp.mapping.udp.role import Manager
                 from pysnmp.proto.api import alpha
-            except ImportError :    
+            except ImportError :
                 logerr("pysnmp doesn't seem to be installed. SNMP checks will be ignored !\n")
                 return 0
-        else :        
+        else :
             hasV4 = True
-            
-        if hasV4 :    
+
+        if hasV4 :
             def retrieveSNMPValues(hostname, community) :
                 """Retrieves a printer's internal page counter and status via SNMP."""
@@ -241,12 +241,12 @@
                                                       cmdgen.UdpTransportTarget((hostname, 161)), \
                                                       tuple([int(i) for i in pageCounterOID.split('.')]))
-                if errorIndication :                                                  
+                if errorIndication :
                     raise "No SNMP !"
-                elif errorStatus :    
+                elif errorStatus :
                     raise "No SNMP !"
-                else :                                 
+                else :
                     self.SNMPOK = True
         else :
-            def retrieveSNMPValues(hostname, community) :    
+            def retrieveSNMPValues(hostname, community) :
                 """Retrieves a printer's internal page counter and status via SNMP."""
                 ver = alpha.protoVersions[alpha.protoVersionId1]
@@ -260,8 +260,8 @@
                                        (hostname, 161), \
                                        (handleAnswer, req))
-                except :    
+                except :
                     raise "No SNMP !"
                 tsp.close()
-            
+
             def handleAnswer(wholemsg, notusedhere, req):
                 """Decodes and handles the SNMP answer."""
@@ -270,5 +270,5 @@
                 try :
                     rsp.berDecode(wholemsg)
-                except TypeMismatchError, msg :    
+                except TypeMismatchError, msg :
                     raise "No SNMP !"
                 else :
@@ -281,24 +281,24 @@
                             for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList():
                                 self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value)
-                            try :    
+                            try :
                                 pagecounter = self.values[0]
                             except :
                                 raise "No SNMP !"
-                            else :    
+                            else :
                                 self.SNMPOK = 1
                                 return 1
-            
+
         self.SNMPOK = 0
         try :
             retrieveSNMPValues(hostname, community)
-        except :    
+        except :
             self.SNMPOK = 0
         return self.SNMPOK
-        
+
     def supportsPJL(self, hostname, port) :
         """Returns 1 if the printer accepts PJL queries over TCP, else 0."""
         def alarmHandler(signum, frame) :
             raise "Timeout !"
-        
+
         pjlsupport = 0
         signal.signal(signal.SIGALRM, alarmHandler)
@@ -311,7 +311,7 @@
             if not answer.startswith("@PJL") :
                 raise "No PJL !"
-        except :    
+        except :
             pass
-        else :    
+        else :
             pjlsupport = 1
         s.close()
@@ -319,11 +319,11 @@
         signal.signal(signal.SIGALRM, signal.SIG_IGN)
         return pjlsupport
-            
-    def hintConfig(self, printers) :    
+
+    def hintConfig(self, printers) :
         """Gives some hints about what to put into pykota.conf"""
         if not printers :
             return
-        sys.stderr.flush() # ensure outputs don't mix    
-        print     
+        sys.stderr.flush() # ensure outputs don't mix
+        print
         print "--- CUT ---"
         print "# Here are some lines that we suggest you add at the end"
@@ -338,19 +338,19 @@
             try :
                 uri = uri.split("cupspykota:", 2)[-1]
-            except (ValueError, IndexError) :    
+            except (ValueError, IndexError) :
                 pass
-            else :    
+            else :
                 while uri and uri.startswith("/") :
                     uri = uri[1:]
                 try :
-                    (backend, destination) = uri.split(":", 1) 
+                    (backend, destination) = uri.split(":", 1)
                     if backend not in ("ipp", "http", "https", "lpd", "socket") :
                         raise ValueError
-                except ValueError :    
+                except ValueError :
                     pass
-                else :        
+                else :
                     while destination.startswith("/") :
                         destination = destination[1:]
-                    checkauth = destination.split("@", 1)    
+                    checkauth = destination.split("@", 1)
                     if len(checkauth) == 2 :
                         destination = checkauth[1]
@@ -362,7 +362,7 @@
                         except ValueError :
                             port = 9100
-                    else :    
+                    else :
                         (hostname, port) = parts[0], 9100
-                        
+
                     if self.supportsSNMP(hostname, "public") :
                         accounter = "hardware(snmp)"
@@ -371,85 +371,85 @@
                     elif self.supportsPJL(hostname, 9101) :
                         accounter = "hardware(pjl:9101)"
-                    elif self.supportsPJL(hostname, port) :    
+                    elif self.supportsPJL(hostname, port) :
                         accounter = "hardware(pjl:%s)" % port
-                    
-            print "preaccounter : software()" 
+
+            print "preaccounter : software()"
             print "accounter : %s" % accounter
             print
         print "--- CUT ---"
-        
+
     def main(self, names, options) :
         """Intializes PyKota's database."""
         self.adminOnly()
-        
+
         if not names :
             names = ["*"]
-            
+
         self.printInfo(_("Please be patient..."))
         dryrun = not options["force"]
         if dryrun :
             self.printInfo(_("Don't worry, the database WILL NOT BE MODIFIED."))
-        else :    
+        else :
             self.printInfo(_("Please WORRY NOW, the database WILL BE MODIFIED."))
-            
-        if options["dousers"] :    
-            if not options["uidmin"] :    
+
+        if options["dousers"] :
+            if not options["uidmin"] :
                 self.printInfo(_("System users will have a print account as well !"), "warn")
                 uidmin = 0
-            else :    
+            else :
                 try :
                     uidmin = int(options["uidmin"])
-                except :    
+                except :
                     try :
                         uidmin = pwd.getpwnam(options["uidmin"])[2]
-                    except KeyError, msg :    
+                    except KeyError, msg :
                         raise PyKotaCommandLineError, _("Unknown username %s : %s") \
                                                    % (options["uidmin"], msg)
-                        
-            if not options["uidmax"] :    
+
+            if not options["uidmax"] :
                 uidmax = sys.maxint
-            else :    
+            else :
                 try :
                     uidmax = int(options["uidmax"])
-                except :    
+                except :
                     try :
                         uidmax = pwd.getpwnam(options["uidmax"])[2]
-                    except KeyError, msg :    
+                    except KeyError, msg :
                         raise PyKotaCommandLineError, _("Unknown username %s : %s") \
                                                    % (options["uidmax"], msg)
-            
-            if uidmin > uidmax :            
+
+            if uidmin > uidmax :
                 (uidmin, uidmax) = (uidmax, uidmin)
             users = self.listUsers(uidmin, uidmax)
-        else :    
+        else :
             users = []
-            
-        if options["dogroups"] :    
-            if not options["gidmin"] :    
+
+        if options["dogroups"] :
+            if not options["gidmin"] :
                 self.printInfo(_("System groups will have a print account as well !"), "warn")
                 gidmin = 0
-            else :    
+            else :
                 try :
                     gidmin = int(options["gidmin"])
-                except :    
+                except :
                     try :
                         gidmin = grp.getgrnam(options["gidmin"])[2]
-                    except KeyError, msg :    
+                    except KeyError, msg :
                         raise PyKotaCommandLineError, _("Unknown groupname %s : %s") \
                                                    % (options["gidmin"], msg)
-                        
-            if not options["gidmax"] :    
+
+            if not options["gidmax"] :
                 gidmax = sys.maxint
-            else :    
+            else :
                 try :
                     gidmax = int(options["gidmax"])
-                except :    
+                except :
                     try :
                         gidmax = grp.getgrnam(options["gidmax"])[2]
-                    except KeyError, msg :    
+                    except KeyError, msg :
                         raise PyKotaCommandLineError, _("Unknown groupname %s : %s") \
                                                    % (options["gidmax"], msg)
-            
-            if gidmin > gidmax :            
+
+            if gidmin > gidmax :
                 (gidmin, gidmax) = (gidmax, gidmin)
             groups = self.listGroups(gidmin, gidmax, users)
@@ -458,7 +458,7 @@
                     if not members :
                         del groups[groupname]
-        else :    
+        else :
             groups = []
-            
+
         printers = self.listPrinters(names)
         if printers :
@@ -466,15 +466,15 @@
             self.createUsers([entry[0] for entry in users], printers, dryrun)
             self.createGroups(groups, printers, dryrun)
-        
+
         if dryrun :
             self.printInfo(_("Simulation terminated."))
-        else :    
+        else :
             self.printInfo(_("Database initialized !"))
-       
-        if options["doconf"] :    
+
+        if options["doconf"] :
             self.hintConfig(printers)
-                    
-                     
-if __name__ == "__main__" : 
+
+
+if __name__ == "__main__" :
     retcode = 0
     try :
@@ -483,9 +483,9 @@
                         "emptygroups", "force", "uidmin=", "uidmax=", \
                         "gidmin=", "gidmax=", "doconf"]
-        
+
         # Initializes the command line tool
         manager = PKTurnKey(doc=__doc__)
         manager.deferredInit()
-        
+
         # parse and checks the command line
         (options, args) = manager.parseCommandline(sys.argv[1:], \
@@ -493,5 +493,5 @@
                                                    long_options, \
                                                    allownothing=1)
-        
+
         # sets long options
         options["help"] = options["h"] or options["help"]
@@ -506,20 +506,20 @@
         options["gidmax"] = options["G"] or options["gidmax"]
         options["doconf"] = options["c"] or options["doconf"]
-        
+
         if options["uidmin"] or options["uidmax"] :
             if not options["dousers"] :
                 manager.printInfo(_("The --uidmin or --uidmax command line option implies --dousers as well."), "warn")
-            options["dousers"] = 1    
-            
+            options["dousers"] = 1
+
         if options["gidmin"] or options["gidmax"] :
             if not options["dogroups"] :
                 manager.printInfo(_("The --gidmin or --gidmax command line option implies --dogroups as well."), "warn")
             options["dogroups"] = 1
-        
+
         if options["dogroups"] :
             if not options["dousers"] :
                 manager.printInfo(_("The --dogroups command line option implies --dousers as well."), "warn")
-            options["dousers"] = 1    
-            
+            options["dousers"] = 1
+
         if options["help"] :
             manager.display_usage_and_quit()
@@ -528,16 +528,16 @@
         else :
             retcode = manager.main(args, options)
-    except KeyboardInterrupt :        
+    except KeyboardInterrupt :
         logerr("\nInterrupted with Ctrl+C !\n")
         retcode = -3
-    except PyKotaCommandLineError, msg :    
+    except PyKotaCommandLineError, msg :
         logerr("%s : %s\n" % (sys.argv[0], msg))
         retcode = -2
-    except SystemExit :        
+    except SystemExit :
         pass
     except :
         try :
             manager.crashed("pkturnkey failed")
-        except :    
+        except :
             crashed("pkturnkey failed")
         retcode = -1
@@ -545,6 +545,6 @@
     try :
         manager.storage.close()
-    except (TypeError, NameError, AttributeError) :    
+    except (TypeError, NameError, AttributeError) :
         pass
-        
-    sys.exit(retcode)    
+
+    sys.exit(retcode)
Index: pykota/trunk/bin/README
===================================================================
--- pykota/trunk/bin/README (revision 3275)
+++ pykota/trunk/bin/README (revision 3413)
@@ -8,10 +8,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/>.
Index: pykota/trunk/bin/repykota
===================================================================
--- pykota/trunk/bin/repykota (revision 3411)
+++ pykota/trunk/bin/repykota (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/>.
@@ -36,6 +36,6 @@
 from pykota.tool import PyKotaTool
 from pykota import reporter
-        
-class RePyKota(PyKotaTool) :        
+
+class RePyKota(PyKotaTool) :
     """A class for repykota."""
     def main(self, ugnames, options) :
@@ -43,5 +43,5 @@
         if options.ingroups and options.groups :
             raise PyKotaCommandLineError, _("Incompatible options, see help.")
-            
+
         if self.config.isAdmin :
             # PyKota administrator
@@ -49,5 +49,5 @@
                 # no username, means all usernames
                 ugnames = [ "*" ]
-                
+
             if options.ingroups :
                 groupsnames = options.ingroups.split(",")
@@ -57,13 +57,13 @@
                     if not group.Exists :
                         self.printInfo("Group %s doesn't exist." % group.Name, "warn")
-                    else :    
+                    else :
                         for user in self.storage.getGroupMembers(group) :
                             members[user.Name] = user
                 ugnames = [ m for m in members.keys() if self.matchString(m, ugnames) ]
-        else :        
+        else :
             # reports only the current user
             if options.ingroups :
                 raise PyKotaCommandLineError, _("Option --ingroups is reserved to PyKota Administrators.")
-                
+
             username = pwd.getpwuid(os.geteuid())[0]
             if options.groups :
@@ -71,17 +71,17 @@
                 if user.Exists :
                     ugnames = [ g.Name for g in self.storage.getUserGroups(user) ]
-                else :    
+                else :
                     ugnames = [ ]
             else :
                 ugnames = [ username ]
-        
+
         printers = self.storage.getMatchingPrinters(options.printer)
         if not printers :
             raise PyKotaCommandLineError, _("There's no printer matching %s") % options.printer
-            
+
         self.reportingtool = reporter.openReporter(self, "text", printers, ugnames, options.groups)
         print self.reportingtool.generateReport()
-                    
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("Minimalist print accounting reports for PyKota. If not launched by a PyKota administrator, additionnal arguments representing users or groups names are ignored, limiting the scope of the reports to the current user."),
                                 usage="repykota [options] [usernames|groupnames]")
@@ -97,5 +97,5 @@
                             default="*",
                             help=_("Acts on this printer only. You can specify several printer names by separating them with commas. The default value is '%default', which means all printers."))
-                            
+
     parser.add_example('',
                        _("This would generate a report for all users on all printers."))
@@ -104,4 +104,4 @@
     parser.add_example('--printer "laser*,*pson" jerome "jo*"',
                        _("This would generate a report for all users named 'jerome' or whose name begins with 'jo', on all printers which name begins with 'laser' or ends with 'pson'."))
-                       
+
     run(parser, RePyKota)
Index: pykota/trunk/bin/warnpykota
===================================================================
--- pykota/trunk/bin/warnpykota (revision 3411)
+++ pykota/trunk/bin/warnpykota (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/>.
@@ -25,5 +25,5 @@
 notifications to users or groups who have reached the limit of their
 printing quota."""
-        
+
 import sys
 import os
@@ -41,12 +41,12 @@
 from pykota.tool import PyKotaTool
 
-class WarnPyKota(PyKotaTool) :        
+class WarnPyKota(PyKotaTool) :
     """A class for warnpykota."""
     def sendMessage(self, adminmail, touser, fullmessage) :
         """Sends an email message containing headers to some user."""
         smtpserver = self.smtpserver
-        try :    
+        try :
             server = smtplib.SMTP(smtpserver)
-        except socket.error, msg :    
+        except socket.error, msg :
             self.printInfo(_("Impossible to connect to SMTP server : %(smtpserver)s") \
                                                 % locals(), \
@@ -55,5 +55,5 @@
             try :
                 server.sendmail(adminmail, [touser], fullmessage)
-            except smtplib.SMTPException, answer :    
+            except smtplib.SMTPException, answer :
                 for (k, v) in answer.recipients.items() :
                     errormsg = v[0]
@@ -63,5 +63,5 @@
                                    "error")
             server.quit()
-            
+
     def sendMessageToUser(self, admin, adminmail, user, subject, message) :
         """Sends an email message to a user."""
@@ -76,5 +76,5 @@
         msg["Date"] = email.Utils.formatdate(localtime=True)
         self.sendMessage(adminmail, usermail, msg.as_string())
-        
+
     def sendMessageToAdmin(self, adminmail, subject, message) :
         """Sends an email message to the Print Quota administrator."""
@@ -86,5 +86,5 @@
         msg["To"] = adminmail
         self.sendMessage(adminmail, adminmail, msg.as_string())
-        
+
     def warnGroupPQuota(self, grouppquota) :
         """Checks a group quota and send messages if quota is exceeded on current printer."""
@@ -98,5 +98,5 @@
         if group.LimitBy in ("noquota", "nochange") :
             action = "ALLOW"
-        else :    
+        else :
             action = self.checkGroupPQuota(grouppquota)
             if action.startswith("POLICY_") :
@@ -111,14 +111,14 @@
                         if mailto != "EXTERNAL" :
                             self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printername))
-                        else :    
+                        else :
                             self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printername))
-            elif action == "WARN" :    
+            elif action == "WARN" :
                 adminmessage = _("Print Quota low for group %(groupname)s on printer %(printername)s") % locals()
                 self.printInfo(adminmessage)
                 if mailto in [ "BOTH", "ADMIN" ] :
                     self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
-                if group.LimitBy and (group.LimitBy.lower() == "balance") : 
+                if group.LimitBy and (group.LimitBy.lower() == "balance") :
                     message = self.config.getPoorWarn()
-                else :     
+                else :
                     message = self.config.getSoftWarn(printername)
                 if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
@@ -126,8 +126,8 @@
                         if mailto != "EXTERNAL" :
                             self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
-                        else :    
+                        else :
                             self.externalMailTo(arguments, action, user, printer, message)
-        return action        
-        
+        return action
+
     def warnUserPQuota(self, userpquota) :
         """Checks a user quota and send him a message if quota is exceeded on current printer."""
@@ -139,5 +139,5 @@
         adminmail = self.config.getAdminMail(printername)
         (mailto, arguments) = self.config.getMailTo(printername)
-        
+
         if user.LimitBy in ("noquota", "nochange") :
             action = "ALLOW"
@@ -149,5 +149,5 @@
                 if mailto != "EXTERNAL" :
                     self.sendMessageToUser(admin, adminmail, user, _("Printing denied."), message)
-                else :    
+                else :
                     self.externalMailTo(arguments, action, user, printer, message)
             if mailto in [ "BOTH", "ADMIN" ] :
@@ -157,5 +157,5 @@
             if action.startswith("POLICY_") :
                 action = action[7:]
-                
+
             if action == "DENY" :
                 adminmessage = _("Print Quota exceeded for user %(username)s on printer %(printername)s") % locals()
@@ -165,24 +165,24 @@
                     if mailto != "EXTERNAL" :
                         self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
-                    else :    
+                    else :
                         self.externalMailTo(arguments, action, user, printer, message)
                 if mailto in [ "BOTH", "ADMIN" ] :
                     self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
-            elif action == "WARN" :    
+            elif action == "WARN" :
                 adminmessage = _("Print Quota low for user %(username)s on printer %(printername)s") % locals()
                 self.printInfo(adminmessage)
                 if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
-                    if user.LimitBy and (user.LimitBy.lower() == "balance") : 
+                    if user.LimitBy and (user.LimitBy.lower() == "balance") :
                         message = self.config.getPoorWarn()
-                    else :     
+                    else :
                         message = self.config.getSoftWarn(printername)
-                    if mailto != "EXTERNAL" :    
+                    if mailto != "EXTERNAL" :
                         self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
-                    else :    
+                    else :
                         self.externalMailTo(arguments, action, user, printer, message)
                 if mailto in [ "BOTH", "ADMIN" ] :
                     self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
-        return action        
-        
+        return action
+
     def main(self, ugnames, options) :
         """Warn users or groups over print quota."""
@@ -192,5 +192,5 @@
                 # no username, means all usernames
                 ugnames = [ "*" ]
-        else :        
+        else :
             # not a PyKota administrator
             # warns only the current user
@@ -203,9 +203,9 @@
                 if user.Exists :
                     ugnames = [ g.Name for g in self.storage.getUserGroups(user) ]
-                else :    
+                else :
                     ugnames = [ ]
             else :
                 ugnames = [ username ]
-                
+
         printername = options.printer
         printers = self.storage.getMatchingPrinters(printername)
@@ -231,6 +231,6 @@
                             if not done :
                                 alreadydone[user.Name] = (action in ('WARN', 'DENY'))
-                     
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("A tool to warn users and groups who have reached the limit of their printing quota."),
                                 usage="warnpykota [options] [usernames|groupnames]")
@@ -243,5 +243,5 @@
                             default="*",
                             help=_("Acts on this printer only. You can specify several printer names by separating them with commas. The default value is '%default', which means all printers."))
-                            
+
     parser.add_example('',
                        _("This would notify all users who have reached the limit of their printing quota on any printer."))
@@ -250,4 +250,4 @@
     parser.add_example('--groups --printer "HP*,XER*" "dev*"',
                        _("This would notify all users of the groups whose name begins with 'dev' and for which the printing quota limit is reached on any printer whose name begins with 'HP' or 'XER'."))
-                       
+
     run(parser, WarnPyKota)
Index: pykota/trunk/bin/pkbanner
===================================================================
--- pykota/trunk/bin/pkbanner (revision 3411)
+++ pykota/trunk/bin/pkbanner (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/>.
@@ -22,5 +22,5 @@
 #
 
-"""A banner generator for PyKota"""        
+"""A banner generator for PyKota"""
 
 import sys
@@ -34,16 +34,16 @@
     import reportlab.lib
     from reportlab.lib.units import cm
-except ImportError :    
+except ImportError :
     hasRL = False
-else :    
+else :
     hasRL = True
-    
+
 try :
-    import PIL.Image 
-except ImportError :    
+    import PIL.Image
+except ImportError :
     hasPIL = False
-else :    
+else :
     hasPIL = True
-    
+
 import pykota.appinit
 from pykota.utils import run
@@ -56,15 +56,15 @@
 from pykota import version
 
-class PyKotaBanner(Tool) :        
+class PyKotaBanner(Tool) :
     """A class for pkbanner."""
-    def getVar(self, varname) :            
+    def getVar(self, varname) :
         """Extracts a variable from the environment and returns its value or 'Unknown' in the current locale."""
         return os.environ.get(varname) or _("Unknown")
-        
+
     def printVar(self, canvas, x, y, label, value, size, savetoner) :
         """Outputs a variable onto the PDF canvas.
-        
+
            Returns the number of points to substract to current Y coordinate.
-        """   
+        """
         canvas.saveState()
         canvas.setFont("Helvetica-Bold", size)
@@ -79,27 +79,27 @@
         canvas.restoreState()
         return (size + 4)
-    
+
     def genPDF(self, pagesize, logo, url, text, savetoner) :
         """Generates the banner in PDF format, return the PDF document as a string."""
         document = cStringIO.StringIO()
         c = canvas.Canvas(document, pagesize=pagesize, pageCompression=1)
-        
+
         c.setAuthor(self.effectiveUserName)
         c.setTitle(_("PyKota generated Banner"))
         c.setSubject(_("This is a print banner generated with PyKota"))
-        
+
         xcenter = pagesize[0] / 2.0
         ycenter = pagesize[1] / 2.0
-                    
-        ypos = pagesize[1] - (2 * cm)            
-        
+
+        ypos = pagesize[1] - (2 * cm)
+
         if logo :
-            try :    
+            try :
                 imglogo = PIL.Image.open(logo)
-            except :    
+            except :
                 self.printInfo("Unable to open image %s" % logo, "warn")
             else :
                 (width, height) = imglogo.size
-                multi = float(width) / (8 * cm) 
+                multi = float(width) / (8 * cm)
                 width = float(width) / multi
                 height = float(height) / multi
@@ -107,78 +107,78 @@
                 ypos -= height
                 c.drawImage(logo, xpos, ypos, width, height)
-        
+
         # New top
         xpos = pagesize[0] / 5.0
         ypos -= (1 * cm) + 20
-        
+
         printername = self.getVar("PYKOTAPRINTERNAME")
         username = self.getVar("PYKOTAUSERNAME")
         accountbanner = self.config.getAccountBanner(printername)
-        
+
         # Outputs the username
-        ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 
-        
-        # Text    
+        ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner)
+
+        # Text
         if text :
-            ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 
-        
+            ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner)
+
         # Printer and Job Id
         job = "%s - %s" % (printername, self.getVar("PYKOTAJOBID"))
-        ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 
-        
+        ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner)
+
         # Current date (TODO : at the time the banner was printed ! Change this to job's submission date)
         datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
-        ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 
-        
+        ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner)
+
         # Result of the print job
         action = self.getVar("PYKOTAACTION")
         if action == "ALLOW" :
             action = _("Allowed")
-        elif action == "DENY" :    
+        elif action == "DENY" :
             action = _("Denied")
-        elif action == "WARN" :    
+        elif action == "WARN" :
             action = _("Allowed with Warning")
-        elif action == "PROBLEM" :    
+        elif action == "PROBLEM" :
             # should never occur
             action = _("Problem")
-        elif action == "CANCEL" :    
+        elif action == "CANCEL" :
             # should never occur
             action = _("Cancelled")
-        ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 
-        
+        ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner)
+
         # skip some space
         ypos -= 20
-        
+
         # Outputs title and filename
         # We put them at x=0.25*pagewidth so that the line is long enough to hold them
         title = self.getVar("PYKOTATITLE")
-        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 
-        
+        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner)
+
         filename = self.getVar("PYKOTAFILENAME")
-        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 
-        
+        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner)
+
         # skip some space
         ypos -= 20
-        
+
         # Now outputs the user's account balance or page counter
-        ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 
+        ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner)
         limitby = self.getVar("PYKOTALIMITBY")
-        if limitby == "balance" :  
-            ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 
+        if limitby == "balance" :
+            ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner)
         elif limitby == "quota" :
-            ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner) 
-            ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner) 
-            ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner) 
+            ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner)
+            ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner)
+            ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner)
         else :
             if limitby == "noquota" :
                 msg = _("No Limit")
-            elif limitby == "nochange" :    
+            elif limitby == "nochange" :
                 msg = _("No Accounting")
-            elif limitby == "noprint" :    
+            elif limitby == "noprint" :
                 msg = _("Forbidden")
-            else :    
+            else :
                 msg = _("Unknown")
             ypos -= self.printVar(c, xcenter, ypos, _("Printing Mode"), msg, 14, savetoner)
-            
+
         # URL
         if url :
@@ -189,9 +189,9 @@
             c.drawCentredString(xcenter, 2 * cm, url)
             c.restoreState()
-        
+
         c.showPage()
         c.save()
         return document.getvalue()
-        
+
     def main(self, arguments, options) :
         """Generates a banner."""
@@ -200,13 +200,13 @@
         if not hasPIL :
             raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
-            
-        self.logdebug("Generating the banner in PDF format...")    
+
+        self.logdebug("Generating the banner in PDF format...")
         doc = self.genPDF(getPageSize(options.pagesize),
-                          options.logo.strip().encode(sys.getfilesystemencoding(), "replace"), 
-                          options.url.strip(), 
-                          " ".join(arguments).strip(), 
+                          options.logo.strip().encode(sys.getfilesystemencoding(), "replace"),
+                          options.url.strip(),
+                          " ".join(arguments).strip(),
                           options.savetoner / 100.0)
-        
-        self.logdebug("Converting the banner to PostScript...")    
+
+        self.logdebug("Converting the banner to PostScript...")
         command = "gs -q -dNOPAUSE -dBATCH -dPARANOIDSAFER -sDEVICE=pswrite -sOutputFile=- -"
         subpr = subprocess.Popen(command,
@@ -215,7 +215,7 @@
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE)
-        try :                         
+        try :
             (out, err) = subpr.communicate(doc)
-        except OSError, msg :    
+        except OSError, msg :
             raise PyKotaToolError, _("Impossible to execute '%(command)s'") % locals()
         status = subpr.wait()
@@ -255,4 +255,4 @@
     parser.add_example('--logo="" --savetoner=75',
                        _("This would generate a banner in the default page size, with no logo, and text luminosity would be increased by 75%."))
-                       
+
     run(parser, PyKotaBanner)
Index: pykota/trunk/bin/autopykota
===================================================================
--- pykota/trunk/bin/autopykota (revision 3411)
+++ pykota/trunk/bin/autopykota (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/>.
@@ -61,10 +61,10 @@
                 if printer.Exists :
                     self.logdebug("Printer %s created successfully." % printername)
-                else :    
+                else :
                     self.logdebug("Impossible to create printer %s." % printername)
                 printernames = [printername]
-            else :    
+            else :
                 printernames = [printer.Name] + [p.Name for p in self.storage.getParentPrinters(printer)]
-            
+
             user = self.storage.getUser(username)
             if not user.Exists :
@@ -72,21 +72,21 @@
                 if (options.email is None) :
                     os.system('pkusers --add --limitby balance --balance "%s" --description "%s" "%s"' \
-                                       % (options.initbalance, 
+                                       % (options.initbalance,
                                           _("user created with autopykota").encode(self.charset, "replace"),
                                           username.encode(self.charset)))
                 else :
                     os.system('pkusers --add --limitby balance --balance "%s" --email "%s" --description "%s" "%s"' \
-                                       % (options.initbalance, 
-                                          options.email.encode(self.charset), 
+                                       % (options.initbalance,
+                                          options.email.encode(self.charset),
                                           _("user created with autopykota").encode(self.charset, "replace"),
                                           username.encode(self.charset)))
-                    
+
                 user = self.storage.getUserFromBackend(username)
                 if user.Exists :
                     self.logdebug("User %s created successfully." % username)
-                else :    
+                else :
                     self.logdebug("Impossible to create user %s." % username)
-                
-            if user.Exists and printer.Exists :    
+
+            if user.Exists and printer.Exists :
                 userpquota = self.storage.getUserPQuota(user, printer)
                 if not userpquota.Exists :
@@ -94,7 +94,7 @@
                                         % (username, printernames))
                     os.system('edpykota --add --printer "%s" "%s"' \
-                                % (','.join(printernames).encode(self.charset), 
+                                % (','.join(printernames).encode(self.charset),
                                    username.encode(self.charset)))
-                    userpquota = self.storage.getUserPQuotaFromBackend(user, 
+                    userpquota = self.storage.getUserPQuotaFromBackend(user,
                                                                        printer)
                     if userpquota.Exists :
@@ -102,5 +102,5 @@
                                             % (username, printername))
                         return 0
-                    else :    
+                    else :
                         self.logdebug("Impossible to create user %s's print quota entry on printer %s." \
                                             % (username, printername))
@@ -110,8 +110,8 @@
                                         % (username, printername))
                     return 0
-            else :        
+            else :
                 return -1
-                
-if __name__ == "__main__" :    
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("A tool to automate user account creation and initial balance setting. THIS TOOL MUST NOT BE USED FROM THE COMMAND LINE BUT ONLY AS PART OF AN external policy IN pykota.conf, AND MUST NOT BE USED IF YOU WANT TO LIMIT YOUR USERS BY PAGE QUOTA !"),
                                 usage="autopykota { -i | --initbalance value } [options]")
@@ -124,7 +124,7 @@
                             dest="email",
                             help=_("Set the user's email address."))
-                            
+
     parser.add_example('--email="@example.com" --initbalance=10.0',
                        _("This would set the current user's email address to $PYKOTAUSERNAME@example.com, and would set the initial value of his account balance to 10.0 credits."))
-                       
+
     run(parser, AutoPyKota)
Index: pykota/trunk/bin/pkprinters
===================================================================
--- pykota/trunk/bin/pkprinters (revision 3411)
+++ pykota/trunk/bin/pkprinters (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/>.
@@ -47,11 +47,11 @@
   -v | --version       Prints pkprinters's version number then exits.
   -h | --help          Prints this message then exits.
-  
-  -a | --add           Adds printers if they don't exist on the Quota 
+
+  -a | --add           Adds printers if they don't exist on the Quota
                        Storage Server. If they exist, they are modified
                        unless -s|--skipexisting is also used.
-                       
+
   -d | --delete        Deletes printers from the quota storage.
-  
+
   -D | --description d Adds a textual description to printers.
 
@@ -62,11 +62,11 @@
                        If both are to be set, separate them with a comma.
                        Floating point and negative values are allowed.
-  
-  -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer 
+
+  -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer
                        groups pg1, pg2, etc... which must already exist.
                        A printer group is just like a normal printer,
                        only that it is usually unknown from the printing
                        system. Create printer groups exactly the same
-                       way that you create printers, then add other 
+                       way that you create printers, then add other
                        printers to them with this option.
                        Accounting is done on a printer and on all
@@ -74,35 +74,35 @@
                        is done on a printer and on all the printer groups
                        it belongs to.
-                       If the --remove option below is not used, the 
+                       If the --remove option below is not used, the
                        default action is to add printers to the specified
                        printer groups.
-                       
+
   -l | --list          List informations about the printer(s) and the
                        printers groups it is a member of.
-                       
-  -r | --remove        In combination with the --groups option above,                       
+
+  -r | --remove        In combination with the --groups option above,
                        remove printers from the specified printers groups.
-                       
+
   -s | --skipexisting  In combination with the --add option above, tells
                        pkprinters to not modify existing printers.
-                       
+
   -m | --maxjobsize s  Sets the maximum job size allowed on the printer
                        to s pages.
-                       
+
   -p | --passthrough   Activate passthrough mode for the printer. In this
                        mode, users are allowed to print without any impact
                        on their quota or account balance.
-                       
+
   -n | --nopassthrough Deactivate passthrough mode for the printer.
-                       Without -p or -n, printers are created in 
+                       Without -p or -n, printers are created in
                        normal mode, i.e. no passthrough.
-  
-  printer1 through printerN can contain wildcards if the --add option 
+
+  printer1 through printerN can contain wildcards if the --add option
   is not set.
-  
-examples :                              
+
+examples :
 
   $ pkprinters --add -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200 hp8000
-  
+
   Will create three printers named hp2100, hp2200 and hp8000.
   Their price per page will be set at 0.05 unit, and their price
@@ -110,37 +110,37 @@
   or whatever you want them to mean.
   All of their descriptions will be set to the string "HP Printer".
-  If any of these printers already exists, it will also be modified 
+  If any of these printers already exists, it will also be modified
   unless the -s|--skipexisting command line option is also used.
-            
+
   $ pkprinters --delete "*"
-  
+
   This will completely delete all printers and associated quota information,
   as well as their job history. USE WITH CARE !
-  
+
   $ pkprinters --groups Laser,HP "hp*"
-  
-  This will put all printers which name matches "hp*" into printers groups 
+
+  This will put all printers which name matches "hp*" into printers groups
   Laser and HP, which MUST already exist.
-  
+
   $ pkprinters --groups LexMark --remove hp2200
-  
+
   This will remove the hp2200 printer from the LexMark printer group.
 """)
-        
-class PKPrinters(PyKotaTool) :        
+
+class PKPrinters(PyKotaTool) :
     """A class for a printers manager."""
     def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) :
         if charges :
-            printer.setPrices(perpage, perjob)    
+            printer.setPrices(perpage, perjob)
         if description is not None :        # NB : "" is allowed !
             printer.setDescription(description)
-        if nopassthrough :    
+        if nopassthrough :
             printer.setPassThrough(False)
-        if passthrough :    
+        if passthrough :
             printer.setPassThrough(True)
         if maxjobsize is not None :
             printer.setMaxJobSize(maxjobsize)
-            
-    def managePrintersGroups(self, pgroups, printer, remove) :        
+
+    def managePrintersGroups(self, pgroups, printer, remove) :
         """Manage printer group membership."""
         for pgroup in pgroups :
@@ -148,7 +148,7 @@
                 pgroup.delPrinterFromGroup(printer)
             else :
-                pgroup.addPrinterToGroup(printer)    
-                
-    def getPrinterDeviceURI(self, printername) :            
+                pgroup.addPrinterToGroup(printer)
+
+    def getPrinterDeviceURI(self, printername) :
         """Returns the Device URI attribute for a particular printer."""
         if not printername :
@@ -159,16 +159,16 @@
         try :
             return cups.doRequest(req).printer["device-uri"][0][1]
-        except :    
+        except :
             self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn")
             return ""
-        
+
     def isPrinterCaptured(self, printername=None, deviceuri=None) :
         """Returns True if the printer is already redirected through PyKota's backend, else False."""
         if (deviceuri or self.getPrinterDeviceURI(printername)).find("cupspykota:") != -1 :
             return True
-        else :    
+        else :
             return False
-        
-    def reroutePrinterThroughPyKota(self, printer) :    
+
+    def reroutePrinterThroughPyKota(self, printer) :
         """Reroutes a CUPS printer through PyKota."""
         uri = self.getPrinterDeviceURI(printer.Name)
@@ -177,6 +177,6 @@
              os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
              self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
-             
-    def deroutePrinterFromPyKota(self, printer) :    
+
+    def deroutePrinterFromPyKota(self, printer) :
         """Deroutes a PyKota printer through CUPS only."""
         uri = self.getPrinterDeviceURI(printer.Name)
@@ -187,15 +187,15 @@
              os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
              self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
-                                     
+
     def main(self, names, options) :
         """Manage printers."""
         if not options["list"] :
             self.adminOnly()
-            
+
         docups = options["cups"]
-        
-        if not options["list"] :    
+
+        if not options["list"] :
             percent = Percent(self)
-            
+
         if not options["add"] :
             if not options["list"] :
@@ -208,7 +208,7 @@
                     percent.display("\n")
                 raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
-            if not options["list"] :    
+            if not options["list"] :
                 percent.setSize(len(printers))
-                
+
         if options["list"] :
             for printer in printers :
@@ -220,8 +220,8 @@
                 print "    %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited")))
                 print "    %s" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO")))
-                if parents : 
+                if parents :
                     print "    %s %s" % (_("in"), parents)
-                print    
-        elif options["delete"] :    
+                print
+        elif options["delete"] :
             percent.display("\n%s..." % _("Deletion"))
             self.storage.deleteManyPrinters(printers)
@@ -233,17 +233,17 @@
                     percent.oneMore()
         else :
-            if options["groups"] :        
+            if options["groups"] :
                 printersgroups = self.storage.getMatchingPrinters(options["groups"])
                 if not printersgroups :
                     raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
-            else :         
+            else :
                 printersgroups = []
-                    
+
             if options["charge"] :
                 try :
                     charges = [float(part) for part in options["charge"].split(',', 1)]
-                except ValueError :    
+                except ValueError :
                     raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
-                else :    
+                else :
                     if len(charges) > 2 :
                         charges = charges[:2]
@@ -251,22 +251,22 @@
                         charges = [charges[0], None]
                     (perpage, perjob) = charges
-            else :        
+            else :
                 charges = perpage = perjob = None
-                    
-            if options["maxjobsize"] :        
+
+            if options["maxjobsize"] :
                 try :
                     maxjobsize = int(options["maxjobsize"])
                     if maxjobsize < 0 :
                         raise ValueError
-                except ValueError :    
+                except ValueError :
                     raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
-            else :        
+            else :
                 maxjobsize = None
-                    
+
             description = options["description"]
             if description :
                 description = description.strip()
-                
-            nopassthrough = options["nopassthrough"]    
+
+            nopassthrough = options["nopassthrough"]
             passthrough = options["passthrough"]
             remove = options["remove"]
@@ -274,5 +274,5 @@
             self.storage.beginTransaction()
             try :
-                if options["add"] :    
+                if options["add"] :
                     percent.display("%s...\n" % _("Creation"))
                     percent.setSize(len(names))
@@ -283,13 +283,13 @@
                                            description, passthrough, \
                                            nopassthrough, maxjobsize)
-                            oldprinter = self.storage.addPrinter(printer)               
-                            
+                            oldprinter = self.storage.addPrinter(printer)
+
                             if docups :
                                  self.reroutePrinterThroughPyKota(printer)
-                                     
+
                             if oldprinter is not None :
                                 if skipexisting :
                                     self.logdebug(_("Printer %s already exists, skipping.") % pname)
-                                else :    
+                                else :
                                     self.logdebug(_("Printer %s already exists, will be modified.") % pname)
                                     self.modifyPrinter(oldprinter, charges, \
@@ -297,34 +297,34 @@
                                                passthrough, nopassthrough, \
                                                maxjobsize)
-                                    oldprinter.save()           
+                                    oldprinter.save()
                                     self.managePrintersGroups(printersgroups, oldprinter, remove)
-                            elif printersgroups :        
+                            elif printersgroups :
                                 self.managePrintersGroups(printersgroups, \
                                                           self.storage.getPrinter(pname), \
                                                           remove)
-                        else :    
+                        else :
                             raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
                         percent.oneMore()
-                else :        
+                else :
                     percent.display("\n%s...\n" % _("Modification"))
-                    for printer in printers :        
+                    for printer in printers :
                         self.modifyPrinter(printer, charges, perpage, perjob, \
                                            description, passthrough, \
                                            nopassthrough, maxjobsize)
-                        printer.save()    
+                        printer.save()
                         self.managePrintersGroups(printersgroups, printer, remove)
                         if docups :
                             self.reroutePrinterThroughPyKota(printer)
                         percent.oneMore()
-            except :                    
+            except :
                 self.storage.rollbackTransaction()
                 raise
-            else :    
+            else :
                 self.storage.commitTransaction()
-                
+
         if not options["list"] :
             percent.done()
-                     
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     retcode = 0
     try :
@@ -334,12 +334,12 @@
                         "skipexisting", "passthrough", "nopassthrough", \
                         "maxjobsize="]
-        
+
         # Initializes the command line tool
         manager = PKPrinters(doc=__doc__)
         manager.deferredInit()
-        
+
         # parse and checks the command line
         (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
-        
+
         # sets long options
         options["help"] = options["h"] or options["help"]
@@ -349,5 +349,5 @@
         options["charge"] = options["c"] or options["charge"]
         options["description"] = options["D"] or options["description"]
-        options["delete"] = options["d"] or options["delete"] 
+        options["delete"] = options["d"] or options["delete"]
         options["groups"] = options["g"] or options["groups"]
         options["list"] = options["l"] or options["list"]
@@ -357,5 +357,5 @@
         options["passthrough"] = options["p"] or options["passthrough"]
         options["nopassthrough"] = options["n"] or options["nopassthrough"]
-        
+
         if options["help"] :
             manager.display_usage_and_quit()
@@ -374,16 +374,16 @@
         else :
             retcode = manager.main(args, options)
-    except KeyboardInterrupt :        
+    except KeyboardInterrupt :
         logerr("\nInterrupted with Ctrl+C !\n")
         retcode = -3
-    except PyKotaCommandLineError, msg :    
+    except PyKotaCommandLineError, msg :
         logerr("%s : %s\n" % (sys.argv[0], msg))
         retcode = -2
-    except SystemExit :        
+    except SystemExit :
         pass
     except :
         try :
             manager.crashed("pkprinters failed")
-        except :    
+        except :
             crashed("pkprinters failed")
         retcode = -1
@@ -391,6 +391,6 @@
     try :
         manager.storage.close()
-    except (TypeError, NameError, AttributeError) :    
+    except (TypeError, NameError, AttributeError) :
         pass
-        
-    sys.exit(retcode)    
+
+    sys.exit(retcode)
Index: pykota/trunk/bin/mailandpopup.sh
===================================================================
--- pykota/trunk/bin/mailandpopup.sh (revision 3275)
+++ pykota/trunk/bin/mailandpopup.sh (revision 3413)
@@ -8,10 +8,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/>.
@@ -37,5 +37,5 @@
 $MESSAGE
 EOF1
-# 
+#
 # Send some information to root as well
 mail -s "Print Quota problem on printer $PNAME" root <<EOF2
@@ -43,5 +43,5 @@
 EOF2
 #
-# Launch WinPopup on user's host (may need a real Samba or NT domain) 
+# Launch WinPopup on user's host (may need a real Samba or NT domain)
 # In some cases the username does not suffice for smbclient to send a message;
 # we must also supply the IP address. This will use smbstatus to get all IPs
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)
Index: pykota/trunk/bin/pknotify
===================================================================
--- pykota/trunk/bin/pknotify (revision 3411)
+++ pykota/trunk/bin/pknotify (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/>.
@@ -30,7 +30,7 @@
 try :
     import PAM
-except ImportError :    
+except ImportError :
     hasPAM = False
-else :    
+else :
     hasPAM = True
 
@@ -53,13 +53,13 @@
   -v | --version             Prints pknotify's version number then exits.
   -h | --help                Prints this message then exits.
-  
+
   -d | --destination h[:p]   Sets the destination hostname and optional
                              port onto which contact the remote PyKotIcon
                              application. This option is mandatory.
                              When not specified, the port defaults to 7654.
-                             
+
   -a | --ask                 Tells pknotify to ask something to the end
                              user. Then pknotify will output the result.
-                       
+
   -C | --checkauth           When --ask is used and both an 'username' and a
                              'password' are asked to the end user, then
@@ -70,9 +70,9 @@
                              be printed. If the user is authenticated, then
                              "USERNAME=xxxx" will be printed as well.
-                             
-  -c | --confirm             Tells pknotify to ask for either a confirmation                       
+
+  -c | --confirm             Tells pknotify to ask for either a confirmation
                              or abortion.
-                             
-  -D | --denyafter N         With --checkauth above, makes pknotify loop                           
+
+  -D | --denyafter N         With --checkauth above, makes pknotify loop
                              up to N times if the password is incorrect.
                              After having reached the limit, "DENY" will
@@ -80,31 +80,31 @@
                              The default value of N is 1, meaning the job
                              is denied after the first unsuccessful try.
-                             
+
   -N | --noremote action     If it's impossible to connect to the remote
                              PyKotIcon machine, do this action instead.
-                             Allowed actions are 'CONTINUE' and 'CANCEL', 
+                             Allowed actions are 'CONTINUE' and 'CANCEL',
                              which will respectively allow the processing
                              of the print job to continue, or the job to
                              be cancelled. The default value is CANCEL.
-                             
+
   -n | --notify              Tells pknotify to send an informational message
                              to the end user.
-                             
+
   -q | --quit                Tells pknotify to send a message asking the
                              PyKotIcon application to exit. This option can
                              be combined with the other ones to make PyKotIcon
                              exit after having sent the answer from the dialog.
-                             
+
   -t | --timeout T           Tells pknotify to ignore the end user's answer if
                              it comes past T seconds after the dialog box being
-                             opened. The default value is 0 seconds, which 
+                             opened. The default value is 0 seconds, which
                              tells pknotify to wait indefinitely.
                              Use this option to avoid having an user who
                              leaved his computer stall a whole print queue.
-                             
+
   You MUST specify either --ask, --confirm, --notify or --quit.
 
-  arguments :             
-  
+  arguments :
+
     -a | --ask : Several arguments are accepted, of the form
                  "label:varname:defaultvalue". The result will
@@ -119,35 +119,35 @@
                  it is not printed, it will be used to check if
                  authentication is valid if you specify --checkauth.
-                 
+
     -c | --confirm : A single argument is expected, representing the
                      message to display. If the dialog is confirmed
                      then pknotify will print OK, else CANCEL.
-                     
-    -n | --notify : A single argument is expected, representing the                 
+
+    -n | --notify : A single argument is expected, representing the
                     message to display. In this case pknotify will
                     always print OK.
-                    
-examples :                    
+
+examples :
 
   pknotify -d client:7654 --noremote CONTINUE --confirm "This job costs 10 credits"
-  
+
   Would display the cost of the print job and asks for confirmation.
   If the end user doesn't have PyKotIcon running and accepting connections
   from the print server, PyKota will consider that the end user accepted
   to print this job.
-  
+
   pknotify --destination $PYKOTAJOBORIGINATINGHOSTNAME:7654 \\
            --checkauth --ask "Your name:username:" "Your password:password:"
-           
-  Asks an username and password, and checks if they are valid.         
+
+  Asks an username and password, and checks if they are valid.
   NB : The PYKOTAJOBORIGINATINGHOSTNAME environment variable is
   only set if you launch pknotify from cupspykota through a directive
   in ~pykota/pykota.conf
-  
+
   The TCP port you'll use must be reachable on the client from the
   print server.
 """)
-        
-class TimeoutError(Exception) :        
+
+class TimeoutError(Exception) :
     """An exception for timeouts."""
     def __init__(self, message = ""):
@@ -157,6 +157,6 @@
         return self.message
     __str__ = __repr__
-    
-class PyKotaNotify(Tool) :        
+
+class PyKotaNotify(Tool) :
     """A class for pknotify."""
     def UTF8ToUserCharset(self, text) :
@@ -164,18 +164,18 @@
         if text is None :
             return None
-        else :    
-            return text.decode("UTF-8", "replace").encode(self.charset, "replace") 
-        
+        else :
+            return text.decode("UTF-8", "replace").encode(self.charset, "replace")
+
     def userCharsetToUTF8(self, text) :
         """Converts from user's charset to UTF-8."""
         if text is None :
             return None
-        else :    
-            return text.decode(self.charset, "replace").encode("UTF-8", "replace")    
-        
+        else :
+            return text.decode(self.charset, "replace").encode("UTF-8", "replace")
+
     def sanitizeMessage(self, msg) :
         """Replaces \\n and returns a messagee in xmlrpclib Binary format."""
         return xmlrpclib.Binary(self.userCharsetToUTF8(msg.replace("\\n", "\n")))
-        
+
     def convPAM(self, auth, queries=[], userdata=None) :
         """Prepares PAM datas."""
@@ -191,9 +191,9 @@
         return response
 
-    def checkAuth(self, username, password) :    
+    def checkAuth(self, username, password) :
         """Checks if we could authenticate an username with a password."""
-        if not hasPAM :    
+        if not hasPAM :
             raise PyKotaToolError, _("You MUST install PyPAM for this functionnality to work !")
-        else :    
+        else :
             retcode = False
             self.password = password
@@ -213,9 +213,9 @@
                 retcode = True
             return retcode
-            
-    def alarmHandler(self, signum, frame) :        
+
+    def alarmHandler(self, signum, frame) :
         """Alarm handler."""
         raise TimeoutError, _("The end user at %s:%i didn't answer within %i seconds. The print job will be cancelled.") % (self.destination, self.port, self.timeout)
-        
+
     def main(self, arguments, options) :
         """Notifies or asks questions to end users through PyKotIcon."""
@@ -226,13 +226,13 @@
             self.destination = options["destination"]
             self.port = 7654
-            
+
         try :
             denyafter = int(options["denyafter"])
             if denyafter < 1 :
                 raise ValueError
-        except (ValueError, TypeError) :        
+        except (ValueError, TypeError) :
             denyafter = 1
-            
-        try :    
+
+        try :
             self.timeout = int(options["timeout"])
             if self.timeout < 0 :
@@ -240,11 +240,11 @@
         except (ValueError, TypeError) :
             self.timeout = 0
-            
+
         if self.timeout :
             signal.signal(signal.SIGALRM, self.alarmHandler)
             signal.alarm(self.timeout)
-            
-        try :    
-            try :    
+
+        try :
+            try :
                 server = xmlrpclib.ServerProxy("http://%s:%s" % (self.destination, self.port))
                 if options["ask"] :
@@ -253,5 +253,5 @@
                         if denyafter < 1 :
                             raise ValueError
-                    except (ValueError, TypeError) :    
+                    except (ValueError, TypeError) :
                         denyafter = 1
                     labels = []
@@ -261,5 +261,5 @@
                         try :
                             (label, varname, varvalue) = arg.split(":", 2)
-                        except ValueError :    
+                        except ValueError :
                             raise PyKotaCommandLineError, "argument '%s' is invalid !" % arg
                         labels.append(self.sanitizeMessage(label))
@@ -267,22 +267,22 @@
                         varnames.append(varname)
                         varvalues[varname] = self.sanitizeMessage(varvalue)
-                        
-                    passnumber = 1    
+
+                    passnumber = 1
                     authok = None
                     while (authok != "AUTH=YES") and (passnumber <= denyafter) :
-                        result = server.askDatas(labels, varnames, varvalues)    
+                        result = server.askDatas(labels, varnames, varvalues)
                         if not options["checkauth"] :
                             break
                         if result["isValid"] :
                             if ("username" in varnames) and ("password" in varnames) :
-                                if self.checkAuth(self.UTF8ToUserCharset(result["username"].data[:]), 
+                                if self.checkAuth(self.UTF8ToUserCharset(result["username"].data[:]),
                                                   self.UTF8ToUserCharset(result["password"].data[:])) :
                                     authok = "AUTH=YES"
-                                else :    
+                                else :
                                     authok = "AUTH=NO"
-                            else :        
-                                authok = "AUTH=IMPOSSIBLE"        
-                        passnumber += 1        
-                                    
+                            else :
+                                authok = "AUTH=IMPOSSIBLE"
+                        passnumber += 1
+
                     if options["checkauth"] and options["denyafter"] \
                        and (passnumber > denyafter) \
@@ -294,12 +294,12 @@
                                and ((varname != "username") or (authok in (None, "AUTH=YES"))) :
                                 print "%s=%s" % (varname.upper(), self.UTF8ToUserCharset(result[varname].data[:]))
-                        if authok is not None :        
-                            print authok        
+                        if authok is not None :
+                            print authok
                 elif options["confirm"] :
                     print server.showDialog(self.sanitizeMessage(arguments[0]), True)
                 elif options["notify"] :
                     print server.showDialog(self.sanitizeMessage(arguments[0]), False)
-                    
-                if options["quit"] :    
+
+                if options["quit"] :
                     server.quitApplication()
             except (xmlrpclib.ProtocolError, socket.error, socket.gaierror), msg :
@@ -309,15 +309,15 @@
                 #except (AttributeError, IndexError) :
                 #    pass
-                #else :    
+                #else :
                 #    if errnum == errno.ECONNREFUSED :
                 #        raise PyKotaToolError, "%s : %s" % (str(msg), (_("Are you sure that PyKotIcon is running and accepting incoming connections on %s:%s ?") % (self.destination, self.port)))
                 self.printInfo("%s : %s" % (_("Connection error"), str(msg)), "warn")
-            except TimeoutError, msg :    
+            except TimeoutError, msg :
                 self.printInfo(msg, "warn")
                 print "CANCEL"      # Timeout occured : job is cancelled.
-        finally :    
-            if self.timeout :    
-                signal.alarm(0)    
-        
+        finally :
+            if self.timeout :
+                signal.alarm(0)
+
 if __name__ == "__main__" :
     retcode = 0
@@ -331,12 +331,12 @@
                         "timeout=", "ask", "checkauth", "confirm", "notify", \
                         "quit", "noremote=" ]
-        
+
         # Initializes the command line tool
         notifier = PyKotaNotify(doc=__doc__)
         notifier.deferredInit()
-        
+
         # parse and checks the command line
         (options, args) = notifier.parseCommandline(sys.argv[1:], short_options, long_options)
-        
+
         # sets long options
         options["help"] = options["h"] or options["help"]
@@ -351,5 +351,5 @@
         options["timeout"] = options["t"] or options["timeout"] or defaults["timeout"]
         options["noremote"] = (options["N"] or options["noremote"] or defaults["noremote"]).upper()
-        
+
         if options["help"] :
             notifier.display_usage_and_quit()
@@ -370,19 +370,19 @@
         else :
             retcode = notifier.main(args, options)
-    except KeyboardInterrupt :        
+    except KeyboardInterrupt :
         logerr("\nInterrupted with Ctrl+C !\n")
         retcode = -3
-    except PyKotaCommandLineError, msg :    
+    except PyKotaCommandLineError, msg :
         logerr("%s : %s\n" % (sys.argv[0], msg))
         print "CANCEL"  # Forces the cancellation of the print job if a command line switch is incorrect
         retcode = -2
-    except SystemExit :        
+    except SystemExit :
         pass
     except :
         try :
             notifier.crashed("%s failed" % sys.argv[0])
-        except :    
+        except :
             crashed("%s failed" % sys.argv[0])
         retcode = -1
-        
-    sys.exit(retcode)    
+
+    sys.exit(retcode)
Index: pykota/trunk/bin/pykosd
===================================================================
--- pykota/trunk/bin/pykosd (revision 3411)
+++ pykota/trunk/bin/pykosd (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/>.
@@ -31,5 +31,5 @@
 try :
     import pyosd
-except ImportError :    
+except ImportError :
     sys.stderr.write("Sorry ! You need both xosd and the Python OSD library (pyosd) for this software to work.\n")
     sys.exit(-1)
@@ -53,12 +53,12 @@
             if not user.Exists :
                 raise PyKotaCommandLineError, _("You %(username)s don't have a PyKota printing account. Please contact your administrator.") % locals()
-            if user.LimitBy == "quota" :    
+            if user.LimitBy == "quota" :
                 printers = self.storage.getMatchingPrinters("*")
                 upquotas = [ self.storage.getUserPQuotaFromBackend(user, p) for p in printers ] # don't use cache
                 nblines = len(upquotas)
-                display = pyosd.osd(font=options.font, 
-                                    colour=color, 
-                                    timeout=options.duration, 
-                                    shadow=2, 
+                display = pyosd.osd(font=options.font,
+                                    colour=color,
+                                    timeout=options.duration,
+                                    shadow=2,
                                     lines=nblines)
                 for line in range(nblines) :
@@ -67,52 +67,52 @@
                         if upq.SoftLimit is None :
                             percent = "%i" % upq.PageCounter
-                        else :        
+                        else :
                             percent = "%i%%" % min((upq.PageCounter * 100) / upq.SoftLimit, 100)
-                    else :        
+                    else :
                         percent = "%i%%" % min((upq.PageCounter * 100) / upq.HardLimit, 100)
-                    printername = upq.Printer.Name    
+                    printername = upq.Printer.Name
                     msg = _("Pages used on %(printername)s : %(percent)s") % locals()
-                    display.display(msg.encode(self.charset, "replace"), 
-                                    type=pyosd.TYPE_STRING, 
+                    display.display(msg.encode(self.charset, "replace"),
+                                    type=pyosd.TYPE_STRING,
                                     line=line)
             elif user.LimitBy == "balance" :
                 if user.AccountBalance <= self.config.getBalanceZero() :
                     color = "#FF0000"
-                display = pyosd.osd(font=options.font, 
-                                    colour=color, 
-                                    timeout=options.duration, 
+                display = pyosd.osd(font=options.font,
+                                    colour=color,
+                                    timeout=options.duration,
                                     shadow=2)
-                balance = user.AccountBalance                    
+                balance = user.AccountBalance
                 msg = _("PyKota Units left : %(balance).2f") % locals()
-                display.display(msg.encode(self.charset, "replace"), 
+                display.display(msg.encode(self.charset, "replace"),
                                 type=pyosd.TYPE_STRING)
-            elif user.LimitBy == "noprint" :    
-                display = pyosd.osd(font=options.font, 
-                                    colour="#FF0000", 
-                                    timeout=options.duration, 
+            elif user.LimitBy == "noprint" :
+                display = pyosd.osd(font=options.font,
+                                    colour="#FF0000",
+                                    timeout=options.duration,
                                     shadow=2)
                 msg = _("Printing denied.")
-                display.display(msg.encode(self.charset, "replace"), 
+                display.display(msg.encode(self.charset, "replace"),
                                 type=pyosd.TYPE_STRING)
-            elif user.LimitBy == "noquota" :    
-                display = pyosd.osd(font=options.font, 
-                                    colour=savecolor, 
-                                    timeout=options.duration, 
+            elif user.LimitBy == "noquota" :
+                display = pyosd.osd(font=options.font,
+                                    colour=savecolor,
+                                    timeout=options.duration,
                                     shadow=2)
                 msg = _("Printing not limited.")
-                display.display(msg.encode(self.charset, "replace"), 
+                display.display(msg.encode(self.charset, "replace"),
                                 type=pyosd.TYPE_STRING)
-            elif user.LimitBy == "nochange" :    
-                display = pyosd.osd(font=options.font, 
-                                    colour=savecolor, 
-                                    timeout=options.duration, 
+            elif user.LimitBy == "nochange" :
+                display = pyosd.osd(font=options.font,
+                                    colour=savecolor,
+                                    timeout=options.duration,
                                     shadow=2)
                 msg = _("Printing not limited, no accounting.")
-                display.display(msg.encode(self.charset, "replace"), 
+                display.display(msg.encode(self.charset, "replace"),
                                 type=pyosd.TYPE_STRING)
-            else :    
+            else :
                 limitby = repr(user.LimitBy)
                 raise PyKotaToolError, "Incorrect limitation factor %(limitby)s for user %(username)s" % locals()
-                
+
             time.sleep(options.duration + 1)
             if loop :
@@ -120,33 +120,33 @@
                 if not loop :
                     break
-            time.sleep(options.sleep)        
-            
-        return 0    
-        
+            time.sleep(options.sleep)
+
+        return 0
+
 if __name__ == "__main__" :
-    def checkandset_positiveint(option, opt, value, optionparser) :    
+    def checkandset_positiveint(option, opt, value, optionparser) :
         """Checks and sets positive integer values."""
         if value < 0 :
             loginvalidparam(opt, value, option.default)
             setattr(optionparser.values, option.dest, option.default)
-        else :    
+        else :
             setattr(optionparser.values, option.dest, value)
-            
-    def checkandset_color(option, opt, value, optionparser) :    
+
+    def checkandset_color(option, opt, value, optionparser) :
         """Checks and sets the color value."""
         if not value.startswith("#") :
             value = "#%s" % value
-        try :    
+        try :
             int(value[1:], 16)
-        except (ValueError, TypeError) :    
+        except (ValueError, TypeError) :
             error = True
-        else :    
+        else :
             error = False
         if (len(value) != 7) or error :
             loginvalidparam(opt, value, option.default)
             setattr(optionparser.values, option.dest, option.default)
-        else :    
+        else :
             setattr(optionparser.values, option.dest, value)
-            
+
     parser = PyKotaOptionParser(description=_("An On Screen Display (OSD) monitor for PyKota's end users."))
     parser.add_option("-c", "--color", "--colour",
@@ -182,7 +182,7 @@
                             default=180,
                             help=_("Set the sleeping time in seconds between two refreshes. Defaults to %default seconds."))
-                            
+
     parser.add_example('-s 60 --loop 5',
                        _("This would tell pykosd to display the current user's status for 3 seconds (the default) every 60 seconds, and exit after 5 iterations."))
-                       
+
     run(parser, PyKOSD)
Index: pykota/trunk/bin/dumpykota
===================================================================
--- pykota/trunk/bin/dumpykota (revision 3411)
+++ pykota/trunk/bin/dumpykota (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/>.
@@ -33,28 +33,28 @@
 from pykota.dumper import DumPyKota
 
-if __name__ == "__main__" : 
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("Data Dumper for PyKota."),
                                 usage="dumpykota [options] [filterexpr]")
-                                
-    parser.add_option("-d", "--data",                            
+
+    parser.add_option("-d", "--data",
                             dest="data",
                             help=_("Select the type of datas to dump. This option is mandatory. Supported data types are : history, payments, billingcodes, users, groups, printers, upquotas, gpquotas, umembers, pmembers, and all. The 'all' value forces the output format to XML."))
-    parser.add_option("-f", "--format",                            
+    parser.add_option("-f", "--format",
                             default="csv",
                             dest="format",
                             help=_("Select the output format, the default being comma separated values. Supported formats are : csv, ssv, tsv, xml and cups. The 'cups' output format only works when dumping the history, and produces CUPS' page_log compatible output."))
-    parser.add_option("-o", "--output",                            
+    parser.add_option("-o", "--output",
                             dest="output",
                             default=u"-",
                             help=_("The name of the file the data dump will be written to. The default value is '-', which tells dumpykota to write the dump to stdout."))
-    parser.add_option("-O", "--orderby",                            
+    parser.add_option("-O", "--orderby",
                             dest="orderby",
                             help=_("Change the ordering of the output based on a comma separated list of ordering statements. For example '-username,+printername' would sort the output by descending order of user names and ascending order of printer names. Not all expressions are supported, and you should not use this if you don't know the internal structure of PyKota's database." ))
-    parser.add_option("-s", "--sum",                            
+    parser.add_option("-s", "--sum",
                             dest="sum",
                             action="store_true",
                             default=False,
                             help=_("Summarize the output. Only available when dumping the printing history or the payments."))
-                                
+
     parser.add_filterexpression("username", _("User's name"))
     parser.add_filterexpression("groupname", _("Users group's name"))
@@ -66,18 +66,18 @@
     parser.add_filterexpression("start", _("Job's date of printing"))
     parser.add_filterexpression("end", _("Job's date of printing"))
-    
-    parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', 
+
+    parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30',
                        _("This would generate a PDF document containing invoices for all users who have spent some credits last month. Amounts would be in EURO and not VAT information would be included."))
 
     parser.add_example("--data history --format csv >myfile.csv",
                        _("This would dump the whole printing history to stdout in the CSV format, and redirect the output to a file."))
-  
+
     parser.add_example("--data users --format xml -o users.xml",
                        _("This would dump all users into the 'users.xml' file in the XML format."))
-  
+
     parser.add_example("--data history printername=HP2100 username=jerome",
                        _("This would dump jerome's printing history on printer HP2100."))
-  
+
     parser.add_example("--data history start=200503 end=20050730234615",
                        _("This would dump all jobs printer between March 1st 2008 at midnight and July 30th 2008 at 23 hours 46 minutes and 15 seconds, included."))
-    run(parser, DumPyKota)                   
+    run(parser, DumPyKota)
Index: pykota/trunk/bin/pkrefund
===================================================================
--- pykota/trunk/bin/pkrefund (revision 3411)
+++ pykota/trunk/bin/pkrefund (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/>.
@@ -34,14 +34,14 @@
     from reportlab.lib import pagesizes
     from reportlab.lib.units import cm
-except ImportError :    
+except ImportError :
     hasRL = False
-else :    
+else :
     hasRL = True
-    
+
 try :
-    import PIL.Image 
-except ImportError :    
+    import PIL.Image
+except ImportError :
     hasPIL = False
-else :    
+else :
     hasPIL = True
 
@@ -54,6 +54,6 @@
 from pykota.errors import PyKotaToolError, PyKotaCommandLineError
 from pykota.tool import Percent, PyKotaTool
-        
-class PKRefund(PyKotaTool) :        
+
+class PKRefund(PyKotaTool) :
     """A class for refund manager."""
     validfilterkeys = [ "username",
@@ -65,10 +65,10 @@
                         "end",
                       ]
-                      
+
     def printVar(self, label, value, size) :
         """Outputs a variable onto the PDF canvas.
-        
+
            Returns the number of points to substract to current Y coordinate.
-        """   
+        """
         xcenter = (self.pagesize[0] / 2.0) - 1*cm
         self.canvas.saveState()
@@ -81,5 +81,5 @@
         self.canvas.restoreState()
         self.ypos -= (size + 4)
-        
+
     def pagePDF(self, receiptnumber, name, values, unit, reason) :
         """Generates a new page in the PDF document."""
@@ -92,5 +92,5 @@
             datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
             self.printVar(_("Edited on"), datetime, 14)
-                
+
             self.ypos -= 20
             self.printVar(_("Jobs refunded"), str(values["nbjobs"]), 18)
@@ -101,31 +101,31 @@
             self.canvas.showPage()
             return 1
-        return 0    
-        
+        return 0
+
     def initPDF(self, logo) :
         """Initializes the PDF document."""
-        self.pdfDocument = cStringIO.StringIO()        
+        self.pdfDocument = cStringIO.StringIO()
         self.canvas = c = canvas.Canvas(self.pdfDocument, \
                                         pagesize=self.pagesize, \
                                         pageCompression=1)
-        
+
         c.setAuthor(self.effectiveUserName)
         c.setTitle(_("PyKota print job refunding receipts"))
         c.setSubject(_("Print job refunding receipts generated with PyKota"))
-        
+
         self.canvas.beginForm("background")
         self.canvas.saveState()
-        
-        self.ypos = self.pagesize[1] - (2 * cm)            
-        
+
+        self.ypos = self.pagesize[1] - (2 * cm)
+
         xcenter = self.pagesize[0] / 2.0
         if logo :
-            try :    
+            try :
                 imglogo = PIL.Image.open(logo)
-            except IOError :    
+            except IOError :
                 self.printInfo("Unable to open image %s" % logo, "warn")
             else :
                 (width, height) = imglogo.size
-                multi = float(width) / (8 * cm) 
+                multi = float(width) / (8 * cm)
                 width = float(width) / multi
                 height = float(height) / multi
@@ -134,5 +134,5 @@
                                   self.ypos, \
                                   width, height)
-        
+
         self.ypos -= (cm + 20)
         self.canvas.setFont("Helvetica-Bold", 14)
@@ -140,20 +140,20 @@
         msg = _("Here's the receipt for the refunding of your print jobs")
         self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
-        
+
         self.yorigine = self.ypos
         self.canvas.restoreState()
         self.canvas.endForm()
-        
-    def endPDF(self, fname) :    
+
+    def endPDF(self, fname) :
         """Flushes the PDF generator."""
         self.canvas.save()
-        if fname != "-" :        
+        if fname != "-" :
             outfile = open(fname, "w")
             outfile.write(self.pdfDocument.getvalue())
             outfile.close()
-        else :    
+        else :
             sys.stdout.write(self.pdfDocument.getvalue())
             sys.stdout.flush()
-        
+
     def genReceipts(self, peruser, logo, outfname, firstnumber, reason, unit) :
         """Generates the receipts file."""
@@ -162,5 +162,5 @@
             if outfname != "-" :
                 percent.display("%s...\n" % _("Generating receipts"))
-                
+
             self.initPDF(logo)
             number = firstnumber
@@ -169,11 +169,11 @@
                 if outfname != "-" :
                     percent.oneMore()
-                    
+
             if number > firstnumber :
                 self.endPDF(outfname)
-                
+
             if outfname != "-" :
                 percent.done()
-        
+
     def main(self, arguments, options) :
         """Refunds jobs."""
@@ -182,12 +182,12 @@
         if not hasPIL :
             raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
-            
-        self.adminOnly()    
-            
+
+        self.adminOnly()
+
         self.pagesize = getPageSize(options.pagesize)
-        
+
         if (not options.reason) or (not options.reason.strip()) :
             raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.")
-            
+
         extractonly = {}
         for filterexp in arguments :
@@ -197,36 +197,36 @@
                     filterkey = filterkey.lower()
                     if filterkey not in self.validfilterkeys :
-                        raise ValueError                
-                except ValueError :    
+                        raise ValueError
+                except ValueError :
                     raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
-                else :    
+                else :
                     extractonly.update({ filterkey : filtervalue })
-            
+
         percent = Percent(self)
         outfname = options.output.strip().encode(sys.getfilesystemencoding())
         if outfname != "-" :
             percent.display("%s..." % _("Extracting datas"))
-        else :    
+        else :
             options.force = True
             self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn")
-            
-        username = extractonly.get("username")    
+
+        username = extractonly.get("username")
         if username :
             user = self.storage.getUser(username)
         else :
             user = None
-            
-        printername = extractonly.get("printername")    
+
+        printername = extractonly.get("printername")
         if printername :
             printer = self.storage.getPrinter(printername)
-        else :    
+        else :
             printer = None
-            
+
         start = extractonly.get("start")
         end = extractonly.get("end")
         (start, end) = self.storage.cleanDates(start, end)
-        
-        jobs = self.storage.retrieveHistory(user=user,    
-                                            printer=printer, 
+
+        jobs = self.storage.retrieveHistory(user=user,
+                                            printer=printer,
                                             hostname=extractonly.get("hostname"),
                                             billingcode=extractonly.get("billingcode"),
@@ -235,13 +235,13 @@
                                             end=end,
                                             limit=0)
-                                            
-        peruser = {}                                    
-        nbjobs = 0                                    
-        nbpages = 0                                            
+
+        peruser = {}
+        nbjobs = 0
+        nbpages = 0
         nbcredits = 0.0
         percent.setSize(len(jobs))
         if outfname != "-" :
             percent.display("\n")
-        for job in jobs :                                    
+        for job in jobs :
             if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
                 if options.force :
@@ -256,5 +256,5 @@
                     if outfname != "-" :
                         percent.oneMore()
-                else :    
+                else :
                     print _("Date : %s") % str(job.JobDate)[:19]
                     print _("Printer : %s") % job.PrinterName
@@ -266,6 +266,6 @@
                     print _("Pages : %i") % job.JobSize
                     print _("Credits : %.3f") % job.JobPrice
-                    
-                    while True :                             
+
+                    while True :
                         answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper()
                         if answer == _("Y") :
@@ -278,22 +278,22 @@
                             counters["nbjobs"] += 1
                             nbjobs += 1
-                            break    
-                        elif answer == _("N") :    
                             break
-                    print        
+                        elif answer == _("N") :
+                            break
+                    print
         if outfname != "-" :
             percent.done()
-        self.genReceipts(peruser, 
-                         options.logo.strip().encode(sys.getfilesystemencoding()), 
-                         outfname, 
-                         options.number, 
-                         options.reason, 
+        self.genReceipts(peruser,
+                         options.logo.strip().encode(sys.getfilesystemencoding()),
+                         outfname,
+                         options.number,
+                         options.reason,
                          options.unit or _("Credits"))
-        if outfname != "-" :    
+        if outfname != "-" :
             nbusers = len(peruser)
             print _("Refunded %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
                      % locals()
-            
-if __name__ == "__main__" : 
+
+if __name__ == "__main__" :
     parser = PyKotaOptionParser(description=_("Refunding tool for PyKota."),
                                 usage="pkrefund [options] [filterexpr]")
@@ -313,5 +313,5 @@
                             default=u"A4",
                             help=_("Set the size of the page. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default page size is %default."))
-    parser.add_option("-n", "--number",                        
+    parser.add_option("-n", "--number",
                             dest="number",
                             type="int",
@@ -329,12 +329,12 @@
                             type="string",
                             help=_("The reason why there was a refund."))
-                            
+
     # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861
     # TODO : we can't use 'default=_("Credits")' for this option
-    parser.add_option("-u", "--unit",                   
+    parser.add_option("-u", "--unit",
                             dest="unit",
                             type="string",
                             help=_("The name of the unit to use on the receipts. The default value is 'Credits' or its locale translation."))
-                            
+
     parser.add_filterexpression("username", _("User's name"))
     parser.add_filterexpression("printername", _("Printer's name"))
@@ -344,17 +344,17 @@
     parser.add_filterexpression("start", _("Job's date of printing"))
     parser.add_filterexpression("end", _("Job's date of printing"))
-    
+
     parser.add_example('--output /tmp/receipts.pdf jobid=503',
                        _("This would refund all jobs which Id is 503. A confirmation would be asked for each job to refund, and a PDF file named /tmp/receipts.pdf would be created containing printable receipts. BEWARE of job ids rolling over if you reset CUPS' history."))
-  
+
     parser.add_example('--reason "Hardware problem" jobid=503 start=today-7',
                        _("This would refund all jobs which id is 503 but which would have been printed during the  past week. The reason would be marked as being an hardware problem."))
-  
+
     parser.add_example('--force username=jerome printername=HP2100',
                        _("This would refund all jobs printed by user jerome on printer HP2100. No confirmation would be asked."))
-  
+
     parser.add_example('--force printername=HP2100 start=200602 end=yesterday',
                        _("This would refund all jobs printed on printer HP2100 between February 1st 2006 and yesterday. No confirmation would be asked."))
-    
+
     (options, arguments) = parser.parse_args()
-    run(parser, PKRefund)                   
+    run(parser, PKRefund)
