Index: /pykota/trunk/conf/pykota.conf.sample
===================================================================
--- /pykota/trunk/conf/pykota.conf.sample (revision 2062)
+++ /pykota/trunk/conf/pykota.conf.sample (revision 2066)
@@ -755,2 +755,22 @@
 # SOFTWARE.
 trustjobsize : yes
+
+# Should we deny duplicate jobs ?
+#
+# A duplicate is a job sent twice (or more) in a row to the same printer
+# by the same user.
+#
+# This can be defined either globally or on a per printer basis
+# The default value is 'no', meaning that duplicate jobs are
+# allowed.
+#
+# NB : if an user prints a job, a second user prints another
+#      job, and the first user prints the first job again,
+#      this is NOT considered as a duplicate since the two
+#      identical jobs printed by the first user are not
+#      one just after the other.
+#
+# Possible values are 'yes' and 'no'
+#
+denyduplicates : no
+
Index: /pykota/trunk/bin/cupspykota
===================================================================
--- /pykota/trunk/bin/cupspykota (revision 2062)
+++ /pykota/trunk/bin/cupspykota (revision 2066)
@@ -24,4 +24,9 @@
 #
 # $Log$
+# Revision 1.89  2005/02/16 00:29:33  jalet
+# Fixed the maxdenybanners directive.
+# Introduced the denyduplicates directive.
+# Fixed some database related glitches.
+#
 # Revision 1.88  2005/02/14 23:39:50  jalet
 # Introduces the new 'trustjobsize' directive to workaround some printers
@@ -534,10 +539,16 @@
             self.logdebug("Precomputed job's size is %s pages, price is %s units" % (self.softwareJobSize, self.softwareJobPrice))
             
-            # if no data to pass to real backend, probably a filter
-            # higher in the chain failed because of a misconfiguration.
-            # we deny the job in this case (nothing to print anyway)
             if not self.jobSizeBytes :
+                # if no data to pass to real backend, probably a filter
+                # higher in the chain failed because of a misconfiguration.
+                # we deny the job in this case (nothing to print anyway)
                 self.printMoreInfo(user, printer, _("Job contains no data. Printing is denied."), "warn")
                 action = "DENY"
+            elif self.config.getDenyDuplicates(printer.Name) \
+                 and printer.LastJob.Exists \
+                 and (printer.LastJob.UserName == user.Name) \
+                 and (printer.LastJob.JobMD5Sum == self.checksum) :
+                self.printMoreInfo(user, printer, _("Job is a duplicate. Printing is denied."), "warn")
+                action = "DENY" 
             else :    
                 # checks the user's quota
@@ -557,9 +568,10 @@
             accountbanner = self.config.getAccountBanner(printer.Name)
             if accountbanner in ["ENDING", "NONE"] :
-                if (action == 'DENY') and (userpquota.WarnCount < self.config.getMaxDenyBanners()) :
+                if (action == 'DENY') and (userpquota.WarnCount >= self.config.getMaxDenyBanners(printer.Name)) :
                     self.printInfo(_("Banner won't be printed : maximum number of deny banners reached."), "warn")
                 else :
                     if action == 'DENY' :
-                        userpquota.warn() # increments the warning counter
+                        self.logdebug("Incrementing the number of deny banners for user %s on printer %s" % (user.Name, printer.Name))
+                        userpquota.incDenyBannerCounter() # increments the warning counter
                         self.exportUserInfo(userpquota)
                     banner = self.startingBanner(printer.Name)
@@ -573,9 +585,10 @@
             # handle starting banner pages during accounting
             if accountbanner in ["STARTING", "BOTH"] :
-                if (action == 'DENY') and (userpquota.WarnCount < self.config.getMaxDenyBanners()) :
+                if (action == 'DENY') and (userpquota.WarnCount >= self.config.getMaxDenyBanners(printer.Name)) :
                     self.printInfo(_("Banner won't be printed : maximum number of deny banners reached."), "warn")
                 else :
                     if action == 'DENY' :
-                        userpquota.warn() # increments the warning counter
+                        self.logdebug("Incrementing the number of deny banners for user %s on printer %s" % (user.Name, printer.Name))
+                        userpquota.incDenyBannerCounter() # increments the warning counter
                         self.exportUserInfo(userpquota)
                     banner = self.startingBanner(printer.Name)
@@ -604,9 +617,10 @@
             # handle ending banner pages during accounting
             if accountbanner in ["ENDING", "BOTH"] :
-                if (action == 'DENY') and (userpquota.WarnCount < self.config.getMaxDenyBanners()) :
+                if (action == 'DENY') and (userpquota.WarnCount >= self.config.getMaxDenyBanners(printer.Name)) :
                     self.printInfo(_("Banner won't be printed : maximum number of deny banners reached."), "warn")
                 else :
                     if action == 'DENY' :
-                        userpquota.warn() # increments the warning counter
+                        self.logdebug("Incrementing the number of deny banners for user %s on printer %s" % (user.Name, printer.Name))
+                        userpquota.incDenyBannerCounter() # increments the warning counter
                         self.exportUserInfo(userpquota)
                     banner = self.endingBanner(printer.Name)
@@ -626,4 +640,5 @@
                 self.printMoreInfo(user, printer, _("Job size forced to 0 because printing is denied."))
             else :    
+                userpquota.resetDenyBannerCounter()
                 jobsize = self.accounter.getJobSize(printer)
                 if self.softwareJobSize and (jobsize != self.softwareJobSize) :
@@ -664,9 +679,10 @@
             # handle ending banner pages after accounting ends
             if accountbanner in ["STARTING", "NONE"] :
-                if (action == 'DENY') and (userpquota.WarnCount < self.config.getMaxDenyBanners()) :
+                if (action == 'DENY') and (userpquota.WarnCount >= self.config.getMaxDenyBanners(printer.Name)) :
                     self.printInfo(_("Banner won't be printed : maximum number of deny banners reached."), "warn")
                 else :
                     if action == 'DENY' :
-                        userpquota.warn() # increments the warning counter
+                        self.logdebug("Incrementing the number of deny banners for user %s on printer %s" % (user.Name, printer.Name))
+                        userpquota.incDenyBannerCounter() # increments the warning counter
                         self.exportUserInfo(userpquota)
                     banner = self.endingBanner(printer.Name)
Index: /pykota/trunk/bin/lprngpykota
===================================================================
--- /pykota/trunk/bin/lprngpykota (revision 2062)
+++ /pykota/trunk/bin/lprngpykota (revision 2066)
@@ -24,4 +24,9 @@
 #
 # $Log$
+# Revision 1.17  2005/02/16 00:29:33  jalet
+# Fixed the maxdenybanners directive.
+# Introduced the denyduplicates directive.
+# Fixed some database related glitches.
+#
 # Revision 1.16  2005/02/14 23:39:50  jalet
 # Introduces the new 'trustjobsize' directive to workaround some printers
@@ -156,4 +161,10 @@
             self.printMoreInfo(user, printer, _("Job contains no data. Printing is denied."), "warn")
             action = "DENY"
+        elif self.config.getDenyDuplicates(printer.Name) \
+             and printer.LastJob.Exists \
+             and (printer.LastJob.UserName == user.Name) \
+             and (printer.LastJob.JobMD5Sum == self.checksum) :
+            self.printMoreInfo(user, printer, _("Job is a duplicate. Printing is denied."), "warn")
+            action = "DENY" 
         else :    
             # checks the user's quota
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 2062)
+++ /pykota/trunk/NEWS (revision 2066)
@@ -22,4 +22,12 @@
 PyKota NEWS :
        
+    - 1.21alpha26 :
+    
+        - Fixed the handling of the 'maxdenybanners' directive.
+        
+        - Introduced the 'denyduplicates' directive.
+        
+        - Fixed some database related glitches.
+        
     - 1.21alpha25 :
     
Index: /pykota/trunk/TODO
===================================================================
--- /pykota/trunk/TODO (revision 2063)
+++ /pykota/trunk/TODO (revision 2066)
@@ -38,8 +38,4 @@
         - Web enabled pykotme.
         
-        - Uses the MD5 checksum of the job's datas which is now
-          saved into the database to be able to forbid duplicate
-          jobs.
-          
         - Maybe put "gracedelay" in the database.
         
Index: /pykota/trunk/pykota/storages/sql.py
===================================================================
--- /pykota/trunk/pykota/storages/sql.py (revision 2057)
+++ /pykota/trunk/pykota/storages/sql.py (revision 2066)
@@ -22,4 +22,9 @@
 #
 # $Log$
+# Revision 1.67  2005/02/16 00:29:33  jalet
+# Fixed the maxdenybanners directive.
+# Introduced the denyduplicates directive.
+# Fixed some database related glitches.
+#
 # Revision 1.66  2005/02/13 22:48:38  jalet
 # Added the md5sum to the history
@@ -319,5 +324,5 @@
         userpquota = StorageUserPQuota(self, user, printer)
         if printer.Exists and user.Exists :
-            result = self.doSearch("SELECT id, lifepagecounter, pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(user.ident), self.doQuote(printer.ident)))
+            result = self.doSearch("SELECT * FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(user.ident), self.doQuote(printer.ident)))
             if result :
                 fields = result[0]
@@ -336,5 +341,5 @@
         grouppquota = StorageGroupPQuota(self, group, printer)
         if group.Exists :
-            result = self.doSearch("SELECT id, softlimit, hardlimit, datelimit FROM grouppquota WHERE groupid=%s AND printerid=%s" % (self.doQuote(group.ident), self.doQuote(printer.ident)))
+            result = self.doSearch("SELECT * FROM grouppquota WHERE groupid=%s AND printerid=%s" % (self.doQuote(group.ident), self.doQuote(printer.ident)))
             if result :
                 fields = result[0]
@@ -354,5 +359,5 @@
         """Extracts a printer's last job information."""
         lastjob = StorageLastJob(self, printer)
-        result = self.doSearch("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize, jobprice, filename, title, copies, options, hostname, jobdate FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printer.ident))
+        result = self.doSearch("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize, jobprice, filename, title, copies, options, hostname, jobdate, md5sum, pages, billingcode FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printer.ident))
         if result :
             fields = result[0]
Index: /pykota/trunk/pykota/storage.py
===================================================================
--- /pykota/trunk/pykota/storage.py (revision 2057)
+++ /pykota/trunk/pykota/storage.py (revision 2066)
@@ -22,4 +22,9 @@
 #
 # $Log$
+# Revision 1.70  2005/02/16 00:29:33  jalet
+# Fixed the maxdenybanners directive.
+# Introduced the denyduplicates directive.
+# Fixed some database related glitches.
+#
 # Revision 1.69  2005/02/13 22:48:37  jalet
 # Added the md5sum to the history
@@ -487,8 +492,13 @@
         self.WarnCount = 0
 
-    def warn(self) :
-        """Increases the warn counter for this user quota."""
+    def incDenyBannerCounter(self) :
+        """Increment the deny banner counter for this user quota."""
         self.parent.increaseUserPQuotaWarnCount(self)
         self.WarnCount = (self.WarnCount or 0) + 1
+        
+    def resetDenyBannerCounter(self) :
+        """Resets the deny banner counter for this user quota."""
+        self.parent.writeUserPQuotaWarnCount(self, 0)
+        self.WarnCount = 0
         
     def reset(self) :    
Index: /pykota/trunk/pykota/config.py
===================================================================
--- /pykota/trunk/pykota/config.py (revision 2062)
+++ /pykota/trunk/pykota/config.py (revision 2066)
@@ -22,4 +22,9 @@
 #
 # $Log$
+# Revision 1.61  2005/02/16 00:29:33  jalet
+# Fixed the maxdenybanners directive.
+# Introduced the denyduplicates directive.
+# Fixed some database related glitches.
+#
 # Revision 1.60  2005/02/14 23:39:50  jalet
 # Introduces the new 'trustjobsize' directive to workaround some printers
@@ -604,4 +609,11 @@
         return self.isTrue(self.getGlobalOption("reject_unknown", ignore=1))
         
+    def getDenyDuplicates(self, printername) :          
+        """Returns 1 if we want to deny duplicate jobs, else 0."""
+        try : 
+            return self.isTrue(self.getPrinterOption(printername, "denyduplicates"))
+        except PyKotaConfigError :    
+            return 0
+        
     def getWinbindSeparator(self) :          
         """Returns the winbind separator's value if it is set, else None."""
Index: /pykota/trunk/pykota/version.py
===================================================================
--- /pykota/trunk/pykota/version.py (revision 2062)
+++ /pykota/trunk/pykota/version.py (revision 2066)
@@ -22,5 +22,5 @@
 #
 
-__version__ = "1.21alpha25_unofficial"
+__version__ = "1.21alpha26_unofficial"
 
 __doc__ = """PyKota : a complete Printing Quota Solution for CUPS and LPRng."""
