Index: /pykota/trunk/conf/pykota.conf.sample
===================================================================
--- /pykota/trunk/conf/pykota.conf.sample (revision 2204)
+++ /pykota/trunk/conf/pykota.conf.sample (revision 2217)
@@ -647,4 +647,5 @@
 #                   page was still printed on the current printer.                   
 # PYKOTAOVERCHARGE : user's overcharging factor.
+# PYKOTAJOBBILLING : Job's billing code if present (CUPS only)
 #
 
Index: /pykota/trunk/bin/cupspykota
===================================================================
--- /pykota/trunk/bin/cupspykota (revision 2210)
+++ /pykota/trunk/bin/cupspykota (revision 2217)
@@ -148,6 +148,6 @@
         return dirvalues       
             
-    def getJobOriginatingHostnameFromPageLog(self, cupsconfig, printername, username, jobid) :
-        """Retrieves the job-originating-hostname from the CUPS page_log file if possible."""
+    def getJobInfosFromPageLog(self, cupsconfig, printername, username, jobid) :
+        """Retrieves the job-originating-hostname and job-billing attributes from the CUPS page_log file if possible."""
         pagelogpath = cupsconfig.get("pagelog", "/var/log/cups/page_log")
         self.logdebug("Trying to extract job-originating-host-name from %s" % pagelogpath)
@@ -156,5 +156,5 @@
         except IOError :    
             self.logdebug("Unable to open %s" % pagelogpath)
-            return # no page log or can't read it, originating hostname unknown yet
+            return (None, None) # no page log or can't read it, originating hostname unknown yet
         else :    
             # TODO : read backward so we could take first value seen
@@ -173,7 +173,10 @@
             if matchingline is None :
                 self.logdebug("No matching line found in %s" % pagelogpath)
-                return # correct line not found, job-originating-host-name unknown
+                return (None, None) # correct line not found, job-originating-host-name unknown
             else :    
-                return matchingline.split()[-1]
+                (jobbilling, hostname) = matchingline.split()[-2:]
+                if jobbilling == "-" :
+                    jobbilling = ""
+                return (jobbilling, hostname)   
                 
     def doWork(self, policy, printer, user, userpquota) :    
@@ -212,10 +215,12 @@
                 ippdatafile.close()
             self.dropPriv()    
-            clienthost = ippmessage.get("job-originating-host-name") \
-                         or self.getJobOriginatingHostnameFromPageLog(cupsdconf, printer.Name, user.Name, self.jobid)
+            clienthost = ippmessage.get("job-originating-host-name") 
+            billingcode = ippmessage.get("job-billing")
+            if clienthost is None :
+                (billingcode, clienthost) = self.getJobInfosFromPageLog(cupsdconf, printer.Name, user.Name, self.jobid)
             self.logdebug("Client Hostname : %s" % (clienthost or "Unknown"))    
-            os.environ["PYKOTAJOBORIGINATINGHOSTNAME"] = str(clienthost or "")
-            
-            # TODO : extract username (double check ?) and billing code too
+            self.logdebug("Billing Code : %s" % (billingcode or "None"))    
+            os.environ["PYKOTAJOBORIGINATINGHOSTNAME"] = clienthost or ""
+            os.environ["PYKOTAJOBBILLING"] = billingcode or ""
             
             # enters first phase
@@ -355,5 +360,5 @@
                                     action, jobsize, jobprice, self.preserveinputfile, \
                                     self.title, self.copies, self.options, clienthost, \
-                                    self.jobSizeBytes, self.checksum)
+                                    self.jobSizeBytes, self.checksum, None, billingcode)
             self.printMoreInfo(user, printer, _("Job added to history."))
             
Index: /pykota/trunk/bin/lprngpykota
===================================================================
--- /pykota/trunk/bin/lprngpykota (revision 2210)
+++ /pykota/trunk/bin/lprngpykota (revision 2217)
@@ -141,5 +141,5 @@
                                     action, jobsize, jobprice, self.preserveinputfile, \
                                     self.title, self.copies, self.options, clienthost, \
-                                    self.jobSizeBytes, self.checksum)
+                                    self.jobSizeBytes, self.checksum, None, None) # TODO : pages detail and billing code
             self.printMoreInfo(user, printer, _("Job added to history."))
             
@@ -160,5 +160,5 @@
                                     action, filename=self.preserveinputfile, title=self.title, \
                                     copies=self.copies, options=self.options, clienthost=clienthost, \
-                                    jobsizebytes=self.jobSizeBytes, jobmd5sum=self.checksum)
+                                    jobsizebytes=self.jobSizeBytes, jobmd5sum=self.checksum, jobpages=None, jobbilling=None) # TODO : pages detail and billing code
             self.logdebug("Job added to history during first pass : Job's size and price are still unknown.")
             
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 2216)
+++ /pykota/trunk/NEWS (revision 2217)
@@ -24,4 +24,8 @@
     - 1.22alpha8 :
     
+        - The job billing code if present is now saved into the database,
+          and exported to subprocesses as the PYKOTAJOBBILLING environment
+          variable.
+          
         - Ctrl+C now makes PyKota commands exit without a traceback.
         
Index: /pykota/trunk/pykota/dumper.py
===================================================================
--- /pykota/trunk/pykota/dumper.py (revision 2163)
+++ /pykota/trunk/pykota/dumper.py (revision 2217)
@@ -150,6 +150,7 @@
             copies = entry[fields["copies"]] or 1
             hostname = entry[fields["hostname"]] or ""
+            billingcode = entry[fields["billingcode"]] or "-"
             for pagenum in range(1, jobsize+1) :
-                self.outfile.write("%s %s %s [%s] %s %s - %s\n" % (printername, username, jobid, jobdate, pagenum, copies, hostname))
+                self.outfile.write("%s %s %s [%s] %s %s %s %s\n" % (printername, username, jobid, jobdate, pagenum, copies, billingcode, hostname))
         
     def dumpXml(self, entries, datatype) :    
Index: /pykota/trunk/pykota/storages/sql.py
===================================================================
--- /pykota/trunk/pykota/storages/sql.py (revision 2147)
+++ /pykota/trunk/pykota/storages/sql.py (revision 2217)
@@ -255,5 +255,5 @@
             lastjob.JobMD5Sum = fields.get("md5sum")
             lastjob.JobPages = fields.get("pages")
-            lastjob.JobBillingCode = fields.get("billingcode")
+            lastjob.JobBillingCode = self.databaseToUserCharset(fields.get("billingcode"))
             lastjob.Exists = 1
         return lastjob
@@ -450,20 +450,21 @@
         self.doModify("UPDATE jobhistory SET jobsize=%s, jobprice=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(lastjob.ident)))
         
-    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None) :
+    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None) :
         """Adds a job in a printer's history."""
         if self.privacy :    
             # For legal reasons, we want to hide the title, filename and options
-            title = filename = options = "Hidden because of privacy concerns"
+            title = filename = options = _("Hidden because of privacy concerns")
         filename = self.userCharsetToDatabase(filename)
         title = self.userCharsetToDatabase(title)
         options = self.userCharsetToDatabase(options)
+        jobbilling = self.userCharsetToDatabase(jobbilling)
         if (not self.disablehistory) or (not printer.LastJob.Exists) :
             if jobsize is not None :
-                self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, hostname, jobsizebytes, md5sum) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum)))
+                self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, hostname, jobsizebytes, md5sum, pages, billingcode) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(jobpages), self.doQuote(jobbilling)))
             else :    
-                self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, filename, title, copies, options, hostname, jobsizebytes, md5sum) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum)))
+                self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, filename, title, copies, options, hostname, jobsizebytes, md5sum, pages, billingcode) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(jobpages), self.doQuote(jobbilling)))
         else :        
             # here we explicitly want to reset jobsize to NULL if needed
-            self.doModify("UPDATE jobhistory SET userid=%s, jobid=%s, pagecounter=%s, action=%s, jobsize=%s, jobprice=%s, filename=%s, title=%s, copies=%s, options=%s, hostname=%s, jobsizebytes=%s, md5sum=%s, jobdate=now() WHERE id=%s" % (self.doQuote(user.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(printer.LastJob.ident)))
+            self.doModify("UPDATE jobhistory SET userid=%s, jobid=%s, pagecounter=%s, action=%s, jobsize=%s, jobprice=%s, filename=%s, title=%s, copies=%s, options=%s, hostname=%s, jobsizebytes=%s, md5sum=%s, pages=%s, billingcode=%s, jobdate=now() WHERE id=%s" % (self.doQuote(user.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(jobpages), self.doQuote(jobbilling), self.doQuote(printer.LastJob.ident)))
             
     def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
@@ -534,5 +535,5 @@
                 job.JobMD5Sum = fields.get("md5sum")
                 job.JobPages = fields.get("pages")
-                job.JobBillingCode = fields.get("billingcode")
+                job.JobBillingCode = self.databaseToUserCharset(fields.get("billingcode"))
                 job.UserName = fields.get("username")
                 job.PrinterName = fields.get("printername")
Index: /pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- /pykota/trunk/pykota/storages/ldapstorage.py (revision 2211)
+++ /pykota/trunk/pykota/storages/ldapstorage.py (revision 2217)
@@ -531,5 +531,5 @@
                 lastjob.JobHostName = fields.get("pykotaHostName", [""])[0]
                 lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
-                lastjob.JobBillingCode = fields.get("pykotaBillingCode", [None])[0]
+                lastjob.JobBillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [None])[0])
                 lastjob.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
                 lastjob.JobPages = fields.get("pykotaPages", [""])[0]
@@ -929,5 +929,5 @@
         self.doModify(lastjob.ident, fields)         
         
-    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None) :
+    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None) :
         """Adds a job in a printer's history."""
         if (not self.disablehistory) or (not printer.LastJob.Exists) :
@@ -939,5 +939,5 @@
         if self.privacy :    
             # For legal reasons, we want to hide the title, filename and options
-            title = filename = options = "Hidden because of privacy concerns"
+            title = filename = options = _("Hidden because of privacy concerns")
         fields = {
                    "objectClass" : ["pykotaObject", "pykotaJob"],
@@ -955,4 +955,6 @@
                    "pykotaJobSizeBytes" : str(jobsizebytes),
                    "pykotaMD5Sum" : str(jobmd5sum),
+                   "pykotaPages" : jobpages,            # don't add this attribute if it is not set, so no string conversion
+                   "pykotaBillingCode" : self.userCharsetToDatabase(jobbilling), # don't add this attribute if it is not set, so no string conversion
                  }
         if (not self.disablehistory) or (not printer.LastJob.Exists) :
@@ -1089,5 +1091,5 @@
                 job.JobHostName = fields.get("pykotaHostName", [""])[0]
                 job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
-                job.JobBillingCode = fields.get("pykotaBillingCode", [None])[0]
+                job.JobBillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [None])[0])
                 job.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
                 job.JobPages = fields.get("pykotaPages", [""])[0]
Index: /pykota/trunk/pykota/storage.py
===================================================================
--- /pykota/trunk/pykota/storage.py (revision 2147)
+++ /pykota/trunk/pykota/storage.py (revision 2217)
@@ -146,7 +146,7 @@
             raise AttributeError, name
             
-    def addJobToHistory(self, jobid, user, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None) :
+    def addJobToHistory(self, jobid, user, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None) :
         """Adds a job to the printer's history."""
-        self.parent.writeJobNew(self, user, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, clienthost, jobsizebytes, jobmd5sum)
+        self.parent.writeJobNew(self, user, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, clienthost, jobsizebytes, jobmd5sum, jobpages, jobbilling)
         # TODO : update LastJob object ? Probably not needed.
         
