Index: /pykota/trunk/conf/pykota.conf.sample
===================================================================
--- /pykota/trunk/conf/pykota.conf.sample (revision 1016)
+++ /pykota/trunk/conf/pykota.conf.sample (revision 1021)
@@ -26,7 +26,9 @@
 [global]
 # Storage backend for quotas
-# only PostgreSQL is supported
+# only PGStorage (PostgreSQL) is supported
 # Ldap, MySQL, Berkeley are planned
-storagebackend: postgresql
+
+# the 'postgresql' value is deprecated, use 'pgstorage' instead.
+storagebackend: pgstorage
 
 # Quota Storage Server hostname (and optional port)
@@ -40,7 +42,7 @@
 # Quota Storage administrator's and normal user's names and passwords
 storageadmin: pykotaadmin
-storageuser: pykotauser
 # storageadminpw: Comment out if unused, or set to Quota Storage admin password
-# storageuserpw: Comment out if unused, or set to Quota Storage user password
+
+# NB : storageuser and storageuserpw are not used anymore
 
 # LDAP example :
@@ -57,4 +59,7 @@
 # if the value is not set then the default SYSTEM applies.
 logger: system
+
+# Enable debugging ? Put YES instead here.
+debug : No
 
 # Mail server to use to warn users
Index: /pykota/trunk/docs/installation.sgml
===================================================================
--- /pykota/trunk/docs/installation.sgml (revision 1015)
+++ /pykota/trunk/docs/installation.sgml (revision 1021)
@@ -131,14 +131,12 @@
         To do so, you'll have to feed <application>PostgreSQL</application> with the
         <filename>pykota-x.xx/initscripts/postgresql/pykota-postgresql.sql</filename> file.
-        This file will create a Quota DataBase administrator and a Quota DataBase user
-        in the <application>PostgreSQL</application> system, then create an empty
+        This file will create a Quota DataBase administrator in the <application>PostgreSQL</application> system, then create an empty
         Quota DataBase and set some permissions on it. The Quota DataBase administrator
-        will be able to add printers, users and groups to the Quota DataBase, while
-        the Quota DataBase user will be used to update the print quota usage
-        in the Quota DataBase. None of these two users is present in the Quota Database
-        itself, they are only defined in <application>PostgreSQL</application> and don't
-        have to exist on any system, nor in the Quota DataBase. Their default names
-        are <literal>pykotaadmin</literal> for the administrator and <literal>pykotauser</literal>
-        for the user. The database which will be created will be named <literal>pykota</literal>.
+        is the <application>PostgreSQL</application>'s user used to manage the Quota database.
+        The Quota DataBase Administrator is not present in the Quota Database
+        itself, he is only defined in <application>PostgreSQL</application> and don't
+        have to exist on any system, nor in the Quota DataBase. His default names
+        is <literal>pykotaadmin</literal>. 
+        The database which will be created will be named <literal>pykota</literal> by default.
         <note>
           <title>Note</title>
@@ -179,17 +177,15 @@
         For security reasons, you may want to set passwords in 
         <application>PostgreSQL</application> for the 
-        <literal>pykotaadmin</literal> and <literal>pykotauser</literal> 
-        users. Otherwise any user able to connect to 
+        <literal>pykotaadmin</literal> user.
+        Otherwise any user able to connect to 
         <application>PostgreSQL</application> on your Quota Storage Server 
-        could connect to the quota database as one of them and modify it without problem. 
+        could connect to the quota database as this user, and modify it without problem. 
       </para>
       
       <para>
         To do so, just type the following lines while still being at the <application>psql</application>
-        prompt (replace the passwords values by your own) :
+        prompt (replace the password values by your own) :
         <screen>
 pykota=# ALTER USER pykotaadmin PASSWORD 'somepassword';
-ALTER USER
-pykota=# ALTER USER pykotauser PASSWORD 'anotherpassword';
 ALTER USER
 pykota=# \q
@@ -253,6 +249,8 @@
       
       <para>
-        An <application>LDAP</application> Storage Backend is planned, but it actually
-        doesn't exist. Some people may already be working on this, though. 
+        An <application>LDAP</application> Storage Backend is currently under heavy work.
+        You can already give a look at the LDAP schema which was defined for PyKota.
+        Full support for LDAP in PyKota should be OK by the end of July 2003, if all
+        continues to go well.
       </para>
     </sect2>  
@@ -374,4 +372,10 @@
 
 $Log$
+Revision 1.15  2003/06/10 16:37:54  jalet
+Deletion of the second user which is not needed anymore.
+Added a debug configuration field in /etc/pykota.conf
+All queries can now be sent to the logger in debug mode, this will
+greatly help improve performance when time for this will come.
+
 Revision 1.14  2003/06/05 07:12:29  jalet
 Reorganization of directories
Index: /pykota/trunk/pykota/tool.py
===================================================================
--- /pykota/trunk/pykota/tool.py (revision 973)
+++ /pykota/trunk/pykota/tool.py (revision 1021)
@@ -21,4 +21,10 @@
 #
 # $Log$
+# Revision 1.40  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
 # Revision 1.39  2003/04/29 18:37:54  jalet
 # Pluggable accounting methods (actually doesn't support external scripts)
@@ -188,5 +194,5 @@
 class PyKotaTool :    
     """Base class for all PyKota command line tools."""
-    def __init__(self, asadmin=1, doc="PyKota %s (c) 2003 %s" % (version.__version__, version.__author__)) :
+    def __init__(self, doc="PyKota %s (c) 2003 %s" % (version.__version__, version.__author__)) :
         """Initializes the command line tool."""
         # locale stuff
@@ -200,6 +206,6 @@
         self.documentation = doc
         self.config = config.PyKotaConfig("/etc")
-        self.logger = logger.openLogger(self.config)
-        self.storage = storage.openConnection(self.config, asadmin=asadmin)
+        self.logger = logger.openLogger(self)
+        self.storage = storage.openConnection(self)
         self.smtpserver = self.config.getSMTPServer()
         
Index: /pykota/trunk/pykota/logger.py
===================================================================
--- /pykota/trunk/pykota/logger.py (revision 952)
+++ /pykota/trunk/pykota/logger.py (revision 1021)
@@ -21,4 +21,10 @@
 #
 # $Log$
+# Revision 1.7  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
 # Revision 1.6  2003/04/23 22:13:57  jalet
 # Preliminary support for LPRng added BUT STILL UNTESTED.
@@ -55,7 +61,7 @@
     __str__ = __repr__
 
-def openLogger(config) :
+def openLogger(pykotatool) :
     """Returns the appropriate logger subsystem object."""
-    backend = config.getLoggingBackend()
+    backend = pykotatool.config.getLoggingBackend()
     try :
         if not backend.isalpha() :
Index: /pykota/trunk/pykota/storages/pgstorage.py
===================================================================
--- /pykota/trunk/pykota/storages/pgstorage.py (revision 1021)
+++ /pykota/trunk/pykota/storages/pgstorage.py (revision 1021)
@@ -0,0 +1,404 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS and LPRng
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
+#
+#
+#
+
+import fnmatch
+
+from pykota.storage import PyKotaStorageError
+
+try :
+    import pg
+except ImportError :    
+    import sys
+    # TODO : to translate or not to translate ?
+    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the PygreSQL module installed correctly." % sys.version.split()[0]
+
+class Storage :
+    def __init__(self, pykotatool, host, dbname, user, passwd) :
+        """Opens the PostgreSQL database connection."""
+        self.tool = pykotatool
+        self.debug = pykotatool.config.getDebug()
+        self.closed = 1
+        try :
+            (host, port) = host.split(":")
+            port = int(port)
+        except ValueError :    
+            port = -1         # Use PostgreSQL's default tcp/ip port (5432).
+        
+        try :
+            self.database = pg.connect(host=host, port=port, dbname=dbname, user=user, passwd=passwd)
+            self.closed = 0
+        except pg.error, msg :
+            raise PyKotaStorageError, msg
+        else :    
+            if self.debug :
+                self.tool.logger.log_message("Database opened (host=%s, port=%s, dbname=%s, user=%s)" % (host, port, dbname, user), "debug")
+            
+    def __del__(self) :        
+        """Closes the database connection."""
+        if not self.closed :
+            self.database.close()
+            self.closed = 1
+            if self.debug :
+                self.tool.logger.log_message("Database closed.", "debug")
+        
+    def doQuery(self, query) :
+        """Does a query."""
+        if type(query) in (type([]), type(())) :
+            query = ";".join(query)
+        query = query.strip()    
+        if not query.endswith(';') :    
+            query += ';'
+        self.database.query("BEGIN;")
+        if self.debug :
+            self.tool.logger.log_message("Transaction began.", "debug")
+        try :
+            if self.debug :
+                self.tool.logger.log_message("QUERY : %s" % query, "debug")
+            result = self.database.query(query)
+        except pg.error, msg :    
+            self.database.query("ROLLBACK;")
+            if self.debug :
+                self.tool.logger.log_message("Transaction aborted.", "debug")
+            raise PyKotaStorageError, msg
+        else :    
+            self.database.query("COMMIT;")
+            if self.debug :
+                self.tool.logger.log_message("Transaction committed.", "debug")
+            return result
+        
+    def doQuote(self, field) :
+        """Quotes a field for use as a string in SQL queries."""
+        if type(field) == type(0.0) : 
+            typ = "decimal"
+        elif type(field) == type(0) :    
+            typ = "int"
+        else :    
+            typ = "text"
+        return pg._quote(field, typ)
+        
+    def doParseResult(self, result) :
+        """Returns the result as a list of Python mappings."""
+        if (result is not None) and (result.ntuples() > 0) :
+            return result.dictresult()
+            
+    def getMatchingPrinters(self, printerpattern) :
+        """Returns the list of all printers as tuples (id, name) for printer names which match a certain pattern."""
+        printerslist = []
+        # We 'could' do a SELECT printername FROM printers WHERE printername LIKE ...
+        # but we don't because other storages semantics may be different, so every
+        # storage should use fnmatch to match patterns and be storage agnostic
+        result = self.doQuery("SELECT id, printername FROM printers")
+        result = self.doParseResult(result)
+        if result is not None :
+            for printer in result :
+                if fnmatch.fnmatchcase(printer["printername"], printerpattern) :
+                    printerslist.append((printer["id"], printer["printername"]))
+        return printerslist        
+            
+    def getPrinterId(self, printername) :        
+        """Returns a printerid given a printername."""
+        result = self.doQuery("SELECT id FROM printers WHERE printername=%s" % self.doQuote(printername))
+        try :
+            return self.doParseResult(result)[0]["id"]
+        except TypeError :      # Not found    
+            return
+            
+    def getPrinterPrices(self, printerid) :        
+        """Returns a printer prices per page and per job given a printerid."""
+        result = self.doQuery("SELECT priceperpage, priceperjob FROM printers WHERE id=%s" % self.doQuote(printerid))
+        try :
+            printerprices = self.doParseResult(result)[0]
+            return (printerprices["priceperpage"], printerprices["priceperjob"])
+        except TypeError :      # Not found    
+            return
+            
+    def setPrinterPrices(self, printerid, perpage, perjob) :
+        """Sets prices per job and per page for a given printer."""
+        self.doQuery("UPDATE printers SET priceperpage=%s, priceperjob=%s WHERE id=%s" % (self.doQuote(perpage), self.doQuote(perjob), self.doQuote(printerid)))
+    
+    def getUserId(self, username) :
+        """Returns a userid given a username."""
+        result = self.doQuery("SELECT id FROM users WHERE username=%s" % self.doQuote(username))
+        try :
+            return self.doParseResult(result)[0]["id"]
+        except TypeError :      # Not found
+            return
+            
+    def getGroupId(self, groupname) :
+        """Returns a groupid given a grupname."""
+        result = self.doQuery("SELECT id FROM groups WHERE groupname=%s" % self.doQuote(groupname))
+        try :
+            return self.doParseResult(result)[0]["id"]
+        except TypeError :      # Not found
+            return
+            
+    def getJobHistoryId(self, jobid, userid, printerid) :        
+        """Returns the history line's id given a (jobid, userid, printerid)."""
+        result = self.doQuery("SELECT id FROM jobhistory WHERE jobid=%s AND userid=%s AND printerid=%s" % (self.doQuote(jobid), self.doQuote(userid), self.doQuote(printerid)))
+        try :
+            return self.doParseResult(result)[0]["id"]
+        except TypeError :      # Not found    
+            return
+            
+    def getPrinterUsers(self, printerid) :        
+        """Returns the list of userids and usernames which uses a given printer."""
+        result = self.doQuery("SELECT DISTINCT id, username FROM users WHERE id IN (SELECT userid FROM userpquota WHERE printerid=%s) ORDER BY username" % self.doQuote(printerid))
+        result = self.doParseResult(result)
+        if result is None :
+            return []
+        else :    
+            return [(record["id"], record["username"]) for record in result]
+        
+    def getPrinterGroups(self, printerid) :        
+        """Returns the list of groups which uses a given printer."""
+        result = self.doQuery("SELECT DISTINCT id, groupname FROM groups WHERE id IN (SELECT groupid FROM grouppquota WHERE printerid=%s)" % self.doQuote(printerid))
+        result = self.doParseResult(result)
+        if result is None :
+            return []
+        else :    
+            return [(record["id"], record["groupname"]) for record in result]
+        
+    def getGroupMembersNames(self, groupname) :        
+        """Returns the list of user's names which are member of this group."""
+        groupid = self.getGroupId(groupname)
+        if groupid is None :
+            return []
+        else :
+            result = self.doQuery("SELECT DISTINCT username FROM users WHERE id IN (SELECT userid FROM groupsmembers WHERE groupid=%s)" % self.doQuote(groupid))
+            return [record["username"] for record in (self.doParseResult(result) or [])]
+        
+    def getUserGroupsNames(self, userid) :        
+        """Returns the list of groups' names the user is a member of."""
+        result = self.doQuery("SELECT DISTINCT groupname FROM groups WHERE id IN (SELECT groupid FROM groupsmembers WHERE userid=%s)" % self.doQuote(userid))
+        return [record["groupname"] for record in (self.doParseResult(result) or [])]
+        
+    def addPrinter(self, printername) :        
+        """Adds a printer to the quota storage, returns its id."""
+        self.doQuery("INSERT INTO printers (printername) VALUES (%s)" % self.doQuote(printername))
+        return self.getPrinterId(printername)
+        
+    def addUser(self, username) :        
+        """Adds a user to the quota storage, returns its id."""
+        self.doQuery("INSERT INTO users (username) VALUES (%s)" % self.doQuote(username))
+        return self.getUserId(username)
+        
+    def addGroup(self, groupname) :        
+        """Adds a group to the quota storage, returns its id."""
+        self.doQuery("INSERT INTO groups (groupname) VALUES (%s)" % self.doQuote(groupname))
+        return self.getGroupId(groupname)
+        
+    def addUserPQuota(self, username, printerid) :
+        """Initializes a user print quota on a printer, adds the user to the quota storage if needed."""
+        userid = self.getUserId(username)     
+        if userid is None :    
+            userid = self.addUser(username)
+        uqexists = (self.getUserPQuota(userid, printerid) is not None)    
+        if not uqexists : 
+            self.doQuery("INSERT INTO userpquota (userid, printerid) VALUES (%s, %s)" % (self.doQuote(userid), self.doQuote(printerid)))
+        return (userid, printerid)
+        
+    def addGroupPQuota(self, groupname, printerid) :
+        """Initializes a group print quota on a printer, adds the group to the quota storage if needed."""
+        groupid = self.getGroupId(groupname)     
+        if groupid is None :    
+            groupid = self.addGroup(groupname)
+        gqexists = (self.getGroupPQuota(groupid, printerid) is not None)    
+        if not gqexists : 
+            self.doQuery("INSERT INTO grouppquota (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(groupid), self.doQuote(printerid)))
+        return (groupid, printerid)
+        
+    def increaseUserBalance(self, userid, amount) :    
+        """Increases (or decreases) an user's account balance by a given amount."""
+        self.doQuery("UPDATE users SET balance=balance+(%s), lifetimepaid=lifetimepaid+(%s) WHERE id=%s" % (self.doQuote(amount), self.doQuote(amount), self.doQuote(userid)))
+        
+    def getUserBalance(self, userid) :    
+        """Returns the current account balance for a given user."""
+        result = self.doQuery("SELECT balance, lifetimepaid FROM users WHERE id=%s" % self.doQuote(userid))
+        try :
+            result = self.doParseResult(result)[0]
+        except TypeError :      # Not found    
+            return
+        else :    
+            return (result["balance"], result["lifetimepaid"])
+        
+    def getGroupBalance(self, groupid) :    
+        """Returns the current account balance for a given group, as the sum of each of its users' account balance."""
+        result = self.doQuery("SELECT SUM(balance) AS balance, SUM(lifetimepaid) AS lifetimepaid FROM users WHERE id in (SELECT userid FROM groupsmembers WHERE groupid=%s)" % self.doQuote(groupid))
+        try :
+            result = self.doParseResult(result)[0]
+        except TypeError :      # Not found    
+            return
+        else :    
+            return (result["balance"], result["lifetimepaid"])
+        
+    def getUserLimitBy(self, userid) :    
+        """Returns the way in which user printing is limited."""
+        result = self.doQuery("SELECT limitby FROM users WHERE id=%s" % self.doQuote(userid))
+        try :
+            return self.doParseResult(result)[0]["limitby"]
+        except TypeError :      # Not found    
+            return
+        
+    def getGroupLimitBy(self, groupid) :    
+        """Returns the way in which group printing is limited."""
+        result = self.doQuery("SELECT limitby FROM groups WHERE id=%s" % self.doQuote(groupid))
+        try :
+            return self.doParseResult(result)[0]["limitby"]
+        except TypeError :      # Not found    
+            return
+        
+    def setUserBalance(self, userid, balance) :    
+        """Sets the account balance for a given user to a fixed value."""
+        (current, lifetimepaid) = self.getUserBalance(userid)
+        difference = balance - current
+        self.increaseUserBalance(userid, difference)
+        
+    def limitUserBy(self, userid, limitby) :    
+        """Limits a given user based either on print quota or on account balance."""
+        self.doQuery("UPDATE users SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(userid)))
+        
+    def limitGroupBy(self, groupid, limitby) :    
+        """Limits a given group based either on print quota or on sum of its users' account balances."""
+        self.doQuery("UPDATE groups SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(groupid)))
+        
+    def setUserPQuota(self, userid, printerid, softlimit, hardlimit) :
+        """Sets soft and hard limits for a user quota on a specific printer given (userid, printerid)."""
+        self.doQuery("UPDATE userpquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE userid=%s AND printerid=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userid), self.doQuote(printerid)))
+        
+    def setGroupPQuota(self, groupid, printerid, softlimit, hardlimit) :
+        """Sets soft and hard limits for a group quota on a specific printer given (groupid, printerid)."""
+        self.doQuery("UPDATE grouppquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE groupid=%s AND printerid=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(groupid), self.doQuote(printerid)))
+        
+    def resetUserPQuota(self, userid, printerid) :    
+        """Resets the page counter to zero for a user on a printer. Life time page counter is kept unchanged."""
+        self.doQuery("UPDATE userpquota SET pagecounter=0, datelimit=NULL WHERE userid=%s AND printerid=%s" % (self.doQuote(userid), self.doQuote(printerid)))
+        
+    def resetGroupPQuota(self, groupid, printerid) :    
+        """Resets the page counter to zero for a group on a printer. Life time page counter is kept unchanged."""
+        self.doQuery("UPDATE grouppquota SET pagecounter=0, datelimit=NULL WHERE groupid=%s AND printerid=%s" % (self.doQuote(groupid), self.doQuote(printerid)))
+        
+    def updateUserPQuota(self, userid, printerid, pagecount) :
+        """Updates the used user Quota information given (userid, printerid) and a job size in pages."""
+        jobprice = self.computePrinterJobPrice(printerid, pagecount)
+        queries = []    
+        queries.append("UPDATE userpquota SET lifepagecounter=lifepagecounter+(%s), pagecounter=pagecounter+(%s) WHERE userid=%s AND printerid=%s" % (self.doQuote(pagecount), self.doQuote(pagecount), self.doQuote(userid), self.doQuote(printerid)))
+        queries.append("UPDATE users SET balance=balance-(%s) WHERE id=%s" % (self.doQuote(jobprice), self.doQuote(userid)))
+        self.doQuery(queries)
+        
+    def getUserPQuota(self, userid, printerid) :
+        """Returns the Print Quota information for a given (userid, printerid)."""
+        result = self.doQuery("SELECT lifepagecounter, pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(userid), self.doQuote(printerid)))
+        try :
+            return self.doParseResult(result)[0]
+        except TypeError :      # Not found    
+            return
+        
+    def getGroupPQuota(self, groupid, printerid) :
+        """Returns the Print Quota information for a given (groupid, printerid)."""
+        result = self.doQuery("SELECT softlimit, hardlimit, datelimit FROM grouppquota WHERE groupid=%s AND printerid=%s" % (self.doQuote(groupid), self.doQuote(printerid)))
+        try :
+            grouppquota = self.doParseResult(result)[0]
+        except TypeError :    
+            return
+        else :    
+            result = self.doQuery("SELECT SUM(lifepagecounter) as lifepagecounter, SUM(pagecounter) as pagecounter FROM userpquota WHERE printerid=%s AND userid in (SELECT userid FROM groupsmembers WHERE groupid=%s)" % (self.doQuote(printerid), self.doQuote(groupid)))
+            try :
+                result = self.doParseResult(result)[0]
+            except TypeError :      # Not found    
+                return
+            else :    
+                grouppquota.update({"lifepagecounter": result["lifepagecounter"], "pagecounter": result["pagecounter"]})
+                return grouppquota
+        
+    def setUserDateLimit(self, userid, printerid, datelimit) :
+        """Sets the limit date for a soft limit to become an hard one given (userid, printerid)."""
+        self.doQuery("UPDATE userpquota SET datelimit=%s::TIMESTAMP WHERE userid=%s AND printerid=%s" % (self.doQuote("%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)), self.doQuote(userid), self.doQuote(printerid)))
+        
+    def setGroupDateLimit(self, groupid, printerid, datelimit) :
+        """Sets the limit date for a soft limit to become an hard one given (groupid, printerid)."""
+        self.doQuery("UPDATE grouppquota SET datelimit=%s::TIMESTAMP WHERE groupid=%s AND printerid=%s" % (self.doQuote("%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)), self.doQuote(groupid), self.doQuote(printerid)))
+        
+    def addJobToHistory(self, jobid, userid, printerid, pagecounter, action, jobsize=None) :
+        """Adds a job to the history: (jobid, userid, printerid, last page counter taken from requester)."""
+        self.doQuery("INSERT INTO jobhistory (jobid, userid, printerid, pagecounter, action, jobsize) VALUES (%s, %s, %s, %s, %s, %s)" % (self.doQuote(jobid), self.doQuote(userid), self.doQuote(printerid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize)))
+        return self.getJobHistoryId(jobid, userid, printerid) # in case jobid is not sufficient
+    
+    def updateJobSizeInHistory(self, historyid, jobsize) :
+        """Updates a job size in the history given the history line's id."""
+        self.doQuery("UPDATE jobhistory SET jobsize=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(historyid)))
+    
+    def getPrinterPageCounter(self, printerid) :
+        """Returns the last page counter value for a printer given its id, also returns last username, last jobid and history line id."""
+        result = self.doQuery("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printerid))
+        try :
+            return self.doParseResult(result)[0]
+        except TypeError :      # Not found
+            return
+        
+    def addUserToGroup(self, userid, groupid) :    
+        """Adds an user to a group."""
+        result = self.doQuery("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(groupid), self.doQuote(userid)))
+        try :
+            mexists = self.doParseResult(result)[0]["mexists"]
+        except TypeError :    
+            mexists = 0
+        if not mexists :    
+            self.doQuery("INSERT INTO groupsmembers (groupid, userid) VALUES (%s, %s)" % (self.doQuote(groupid), self.doQuote(userid)))
+        
+    def deleteUser(self, userid) :    
+        """Completely deletes an user from the Quota Storage."""
+        queries = []
+        queries.append("DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(userid))
+        queries.append("DELETE FROM jobhistory WHERE userid=%s" % self.doQuote(userid))
+        queries.append("DELETE FROM userpquota WHERE userid=%s" % self.doQuote(userid))
+        queries.append("DELETE FROM users WHERE id=%s" % self.doQuote(userid))
+        # TODO : What should we do if we delete the last person who used a given printer ?
+        self.doQuery(queries)
+        
+    def deleteGroup(self, groupid) :    
+        """Completely deletes an user from the Quota Storage."""
+        queries = []
+        queries.append("DELETE FROM groupsmembers WHERE groupid=%s" % self.doQuote(groupid))
+        queries.append("DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(groupid))
+        queries.append("DELETE FROM groups WHERE id=%s" % self.doQuote(groupid))
+        self.doQuery(queries)
+        
+    def computePrinterJobPrice(self, printerid, jobsize) :    
+        """Returns the price for a job on a given printer."""
+        # TODO : create a base class with things like this
+        prices = self.getPrinterPrices(printerid)
+        if prices is None :
+            perpage = perjob = 0.0
+        else :    
+            (perpage, perjob) = prices
+        return perjob + (perpage * jobsize)
+        
Index: /pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- /pykota/trunk/pykota/storages/ldapstorage.py (revision 1020)
+++ /pykota/trunk/pykota/storages/ldapstorage.py (revision 1021)
@@ -21,4 +21,10 @@
 #
 # $Log$
+# Revision 1.5  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
 # Revision 1.4  2003/06/10 10:45:32  jalet
 # Not implemented methods now raise an exception when called.
@@ -55,7 +61,9 @@
     
 class Storage :
-    def __init__(self, host, dbname, user, passwd) :
+    def __init__(self, pykotatool, host, dbname, user, passwd) :
         """Opens the LDAP connection."""
         # raise PyKotaStorageError, "Sorry, the LDAP backend for PyKota is not yet implemented !"
+        self.tool = pykotatool
+        self.debug = pykotatool.config.getDebug()
         self.closed = 1
         try :
@@ -67,4 +75,6 @@
         else :    
             self.closed = 0
+            if self.debug :
+                self.tool.logger.log_message("Database opened (host=%s, dbname=%s, user=%s)" % (host, dbname, user), "debug")
             
     def __del__(self) :        
@@ -73,4 +83,6 @@
             del self.database
             self.closed = 1
+            if self.debug :
+                self.tool.logger.log_message("Database closed.", "debug")
         
     def doSearch(self, key, fields, base="", scope=ldap.SCOPE_SUBTREE) :
@@ -78,4 +90,6 @@
         try :
             # prepends something more restrictive at the beginning of the base dn
+            if self.debug :
+                self.tool.logger.log_message("QUERY : BaseDN : %s, Scope : %s, Filter : %s, Attributes : %s" % ((base or self.basedn), scope, key, fields), "debug")
             result = self.database.search_s(base or self.basedn, scope, key, fields)
         except ldap.NO_SUCH_OBJECT :    
Index: /kota/trunk/pykota/storages/sql.py
===================================================================
--- /pykota/trunk/pykota/storages/sql.py (revision 1016)
+++  (revision )
@@ -1,429 +1,0 @@
-# PyKota
-#
-# PyKota : Print Quotas for CUPS and LPRng
-#
-# (c) 2003 Jerome Alet <alet@librelogiciel.com>
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 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, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
-#
-# $Id$
-#
-# $Log$
-# Revision 1.32  2003/06/05 11:19:13  jalet
-# More good work on LDAP storage.
-#
-# Revision 1.31  2003/04/30 13:36:40  jalet
-# Stupid accounting method was added.
-#
-# Revision 1.30  2003/04/27 08:04:15  jalet
-# LDAP storage backend's skeleton added. DOESN'T WORK.
-#
-# Revision 1.29  2003/04/23 22:13:57  jalet
-# Preliminary support for LPRng added BUT STILL UNTESTED.
-#
-# Revision 1.28  2003/04/17 09:26:21  jalet
-# repykota now reports account balances too.
-#
-# Revision 1.27  2003/04/16 12:35:49  jalet
-# Groups quota work now !
-#
-# Revision 1.26  2003/04/16 08:53:14  jalet
-# Printing can now be limited either by user's account balance or by
-# page quota (the default). Quota report doesn't include account balance
-# yet, though.
-#
-# Revision 1.25  2003/04/15 21:58:33  jalet
-# edpykota now accepts a --delete option.
-# Preparation to allow edpykota to accept much more command line options
-# (WARNING : docstring is OK, but code isn't !)
-#
-# Revision 1.24  2003/04/15 13:55:28  jalet
-# Options --limitby and --balance added to edpykota
-#
-# Revision 1.23  2003/04/15 11:30:57  jalet
-# More work done on money print charging.
-# Minor bugs corrected.
-# All tools now access to the storage as priviledged users, repykota excepted.
-#
-# Revision 1.22  2003/04/10 21:47:20  jalet
-# Job history added. Upgrade script neutralized for now !
-#
-# Revision 1.21  2003/04/08 20:38:08  jalet
-# The last job Id is saved now for each printer, this will probably
-# allow other accounting methods in the future.
-#
-# Revision 1.20  2003/03/29 13:45:27  jalet
-# GPL paragraphs were incorrectly (from memory) copied into the sources.
-# Two README files were added.
-# Upgrade script for PostgreSQL pre 1.01 schema was added.
-#
-# Revision 1.19  2003/02/27 08:41:49  jalet
-# DATETIME is not supported anymore in PostgreSQL 7.3 it seems, but
-# TIMESTAMP is.
-#
-# Revision 1.18  2003/02/10 12:07:31  jalet
-# Now repykota should output the recorded total page number for each printer too.
-#
-# Revision 1.17  2003/02/10 08:41:36  jalet
-# edpykota's --reset command line option resets the limit date too.
-#
-# Revision 1.16  2003/02/08 22:39:46  jalet
-# --reset command line option added
-#
-# Revision 1.15  2003/02/08 22:12:09  jalet
-# Life time counter for users and groups added.
-#
-# Revision 1.14  2003/02/07 22:13:13  jalet
-# Perhaps edpykota is now able to add printers !!! Oh, stupid me !
-#
-# Revision 1.13  2003/02/07 00:08:52  jalet
-# Typos
-#
-# Revision 1.12  2003/02/06 23:20:03  jalet
-# warnpykota doesn't need any user/group name argument, mimicing the
-# warnquota disk quota tool.
-#
-# Revision 1.11  2003/02/06 15:05:13  jalet
-# self was forgotten
-#
-# Revision 1.10  2003/02/06 15:03:11  jalet
-# added a method to set the limit date
-#
-# Revision 1.9  2003/02/06 14:52:35  jalet
-# Forgotten import
-#
-# Revision 1.8  2003/02/06 14:49:04  jalet
-# edpykota should be ok now
-#
-# Revision 1.7  2003/02/06 14:28:59  jalet
-# edpykota should be ok, minus some typos
-#
-# Revision 1.6  2003/02/06 09:19:02  jalet
-# More robust behavior (hopefully) when the user or printer is not managed
-# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
-# printer and/or user not 'yet?' in storage.
-#
-# Revision 1.5  2003/02/05 23:26:22  jalet
-# Incorrect handling of grace delay
-#
-# Revision 1.4  2003/02/05 23:02:10  jalet
-# Typo
-#
-# Revision 1.3  2003/02/05 23:00:12  jalet
-# Forgotten import
-# Bad datetime conversion
-#
-# Revision 1.2  2003/02/05 22:28:38  jalet
-# More robust storage
-#
-# Revision 1.1  2003/02/05 21:28:17  jalet
-# Initial import into CVS
-#
-#
-#
-
-import fnmatch
-
-class SQLStorage :    
-    def getMatchingPrinters(self, printerpattern) :
-        """Returns the list of all printers as tuples (id, name) for printer names which match a certain pattern."""
-        printerslist = []
-        # We 'could' do a SELECT printername FROM printers WHERE printername LIKE ...
-        # but we don't because other storages semantics may be different, so every
-        # storage should use fnmatch to match patterns and be storage agnostic
-        result = self.doQuery("SELECT id, printername FROM printers")
-        result = self.doParseResult(result)
-        if result is not None :
-            for printer in result :
-                if fnmatch.fnmatchcase(printer["printername"], printerpattern) :
-                    printerslist.append((printer["id"], printer["printername"]))
-        return printerslist        
-            
-    def getPrinterId(self, printername) :        
-        """Returns a printerid given a printername."""
-        result = self.doQuery("SELECT id FROM printers WHERE printername=%s" % self.doQuote(printername))
-        try :
-            return self.doParseResult(result)[0]["id"]
-        except TypeError :      # Not found    
-            return
-            
-    def getPrinterPrices(self, printerid) :        
-        """Returns a printer prices per page and per job given a printerid."""
-        result = self.doQuery("SELECT priceperpage, priceperjob FROM printers WHERE id=%s" % self.doQuote(printerid))
-        try :
-            printerprices = self.doParseResult(result)[0]
-            return (printerprices["priceperpage"], printerprices["priceperjob"])
-        except TypeError :      # Not found    
-            return
-            
-    def setPrinterPrices(self, printerid, perpage, perjob) :
-        """Sets prices per job and per page for a given printer."""
-        self.doQuery("UPDATE printers SET priceperpage=%s, priceperjob=%s WHERE id=%s" % (self.doQuote(perpage), self.doQuote(perjob), self.doQuote(printerid)))
-    
-    def getUserId(self, username) :
-        """Returns a userid given a username."""
-        result = self.doQuery("SELECT id FROM users WHERE username=%s" % self.doQuote(username))
-        try :
-            return self.doParseResult(result)[0]["id"]
-        except TypeError :      # Not found
-            return
-            
-    def getGroupId(self, groupname) :
-        """Returns a groupid given a grupname."""
-        result = self.doQuery("SELECT id FROM groups WHERE groupname=%s" % self.doQuote(groupname))
-        try :
-            return self.doParseResult(result)[0]["id"]
-        except TypeError :      # Not found
-            return
-            
-    def getJobHistoryId(self, jobid, userid, printerid) :        
-        """Returns the history line's id given a (jobid, userid, printerid)."""
-        result = self.doQuery("SELECT id FROM jobhistory WHERE jobid=%s AND userid=%s AND printerid=%s" % (self.doQuote(jobid), self.doQuote(userid), self.doQuote(printerid)))
-        try :
-            return self.doParseResult(result)[0]["id"]
-        except TypeError :      # Not found    
-            return
-            
-    def getPrinterUsers(self, printerid) :        
-        """Returns the list of userids and usernames which uses a given printer."""
-        result = self.doQuery("SELECT DISTINCT id, username FROM users WHERE id IN (SELECT userid FROM userpquota WHERE printerid=%s) ORDER BY username" % self.doQuote(printerid))
-        result = self.doParseResult(result)
-        if result is None :
-            return []
-        else :    
-            return [(record["id"], record["username"]) for record in result]
-        
-    def getPrinterGroups(self, printerid) :        
-        """Returns the list of groups which uses a given printer."""
-        result = self.doQuery("SELECT DISTINCT id, groupname FROM groups WHERE id IN (SELECT groupid FROM grouppquota WHERE printerid=%s)" % self.doQuote(printerid))
-        result = self.doParseResult(result)
-        if result is None :
-            return []
-        else :    
-            return [(record["id"], record["groupname"]) for record in result]
-        
-    def getGroupMembersNames(self, groupname) :        
-        """Returns the list of user's names which are member of this group."""
-        groupid = self.getGroupId(groupname)
-        if groupid is None :
-            return []
-        else :
-            result = self.doQuery("SELECT DISTINCT username FROM users WHERE id IN (SELECT userid FROM groupsmembers WHERE groupid=%s)" % self.doQuote(groupid))
-            return [record["username"] for record in (self.doParseResult(result) or [])]
-        
-    def getUserGroupsNames(self, userid) :        
-        """Returns the list of groups' names the user is a member of."""
-        result = self.doQuery("SELECT DISTINCT groupname FROM groups WHERE id IN (SELECT groupid FROM groupsmembers WHERE userid=%s)" % self.doQuote(userid))
-        return [record["groupname"] for record in (self.doParseResult(result) or [])]
-        
-    def addPrinter(self, printername) :        
-        """Adds a printer to the quota storage, returns its id."""
-        self.doQuery("INSERT INTO printers (printername) VALUES (%s)" % self.doQuote(printername))
-        return self.getPrinterId(printername)
-        
-    def addUser(self, username) :        
-        """Adds a user to the quota storage, returns its id."""
-        self.doQuery("INSERT INTO users (username) VALUES (%s)" % self.doQuote(username))
-        return self.getUserId(username)
-        
-    def addGroup(self, groupname) :        
-        """Adds a group to the quota storage, returns its id."""
-        self.doQuery("INSERT INTO groups (groupname) VALUES (%s)" % self.doQuote(groupname))
-        return self.getGroupId(groupname)
-        
-    def addUserPQuota(self, username, printerid) :
-        """Initializes a user print quota on a printer, adds the user to the quota storage if needed."""
-        userid = self.getUserId(username)     
-        if userid is None :    
-            userid = self.addUser(username)
-        uqexists = (self.getUserPQuota(userid, printerid) is not None)    
-        if not uqexists : 
-            self.doQuery("INSERT INTO userpquota (userid, printerid) VALUES (%s, %s)" % (self.doQuote(userid), self.doQuote(printerid)))
-        return (userid, printerid)
-        
-    def addGroupPQuota(self, groupname, printerid) :
-        """Initializes a group print quota on a printer, adds the group to the quota storage if needed."""
-        groupid = self.getGroupId(groupname)     
-        if groupid is None :    
-            groupid = self.addGroup(groupname)
-        gqexists = (self.getGroupPQuota(groupid, printerid) is not None)    
-        if not gqexists : 
-            self.doQuery("INSERT INTO grouppquota (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(groupid), self.doQuote(printerid)))
-        return (groupid, printerid)
-        
-    def increaseUserBalance(self, userid, amount) :    
-        """Increases (or decreases) an user's account balance by a given amount."""
-        self.doQuery("UPDATE users SET balance=balance+(%s), lifetimepaid=lifetimepaid+(%s) WHERE id=%s" % (self.doQuote(amount), self.doQuote(amount), self.doQuote(userid)))
-        
-    def getUserBalance(self, userid) :    
-        """Returns the current account balance for a given user."""
-        result = self.doQuery("SELECT balance, lifetimepaid FROM users WHERE id=%s" % self.doQuote(userid))
-        try :
-            result = self.doParseResult(result)[0]
-        except TypeError :      # Not found    
-            return
-        else :    
-            return (result["balance"], result["lifetimepaid"])
-        
-    def getGroupBalance(self, groupid) :    
-        """Returns the current account balance for a given group, as the sum of each of its users' account balance."""
-        result = self.doQuery("SELECT SUM(balance) AS balance, SUM(lifetimepaid) AS lifetimepaid FROM users WHERE id in (SELECT userid FROM groupsmembers WHERE groupid=%s)" % self.doQuote(groupid))
-        try :
-            result = self.doParseResult(result)[0]
-        except TypeError :      # Not found    
-            return
-        else :    
-            return (result["balance"], result["lifetimepaid"])
-        
-    def getUserLimitBy(self, userid) :    
-        """Returns the way in which user printing is limited."""
-        result = self.doQuery("SELECT limitby FROM users WHERE id=%s" % self.doQuote(userid))
-        try :
-            return self.doParseResult(result)[0]["limitby"]
-        except TypeError :      # Not found    
-            return
-        
-    def getGroupLimitBy(self, groupid) :    
-        """Returns the way in which group printing is limited."""
-        result = self.doQuery("SELECT limitby FROM groups WHERE id=%s" % self.doQuote(groupid))
-        try :
-            return self.doParseResult(result)[0]["limitby"]
-        except TypeError :      # Not found    
-            return
-        
-    def setUserBalance(self, userid, balance) :    
-        """Sets the account balance for a given user to a fixed value."""
-        (current, lifetimepaid) = self.getUserBalance(userid)
-        difference = balance - current
-        self.increaseUserBalance(userid, difference)
-        
-    def limitUserBy(self, userid, limitby) :    
-        """Limits a given user based either on print quota or on account balance."""
-        self.doQuery("UPDATE users SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(userid)))
-        
-    def limitGroupBy(self, groupid, limitby) :    
-        """Limits a given group based either on print quota or on sum of its users' account balances."""
-        self.doQuery("UPDATE groups SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(groupid)))
-        
-    def setUserPQuota(self, userid, printerid, softlimit, hardlimit) :
-        """Sets soft and hard limits for a user quota on a specific printer given (userid, printerid)."""
-        self.doQuery("UPDATE userpquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE userid=%s AND printerid=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userid), self.doQuote(printerid)))
-        
-    def setGroupPQuota(self, groupid, printerid, softlimit, hardlimit) :
-        """Sets soft and hard limits for a group quota on a specific printer given (groupid, printerid)."""
-        self.doQuery("UPDATE grouppquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE groupid=%s AND printerid=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(groupid), self.doQuote(printerid)))
-        
-    def resetUserPQuota(self, userid, printerid) :    
-        """Resets the page counter to zero for a user on a printer. Life time page counter is kept unchanged."""
-        self.doQuery("UPDATE userpquota SET pagecounter=0, datelimit=NULL WHERE userid=%s AND printerid=%s" % (self.doQuote(userid), self.doQuote(printerid)))
-        
-    def resetGroupPQuota(self, groupid, printerid) :    
-        """Resets the page counter to zero for a group on a printer. Life time page counter is kept unchanged."""
-        self.doQuery("UPDATE grouppquota SET pagecounter=0, datelimit=NULL WHERE groupid=%s AND printerid=%s" % (self.doQuote(groupid), self.doQuote(printerid)))
-        
-    def updateUserPQuota(self, userid, printerid, pagecount) :
-        """Updates the used user Quota information given (userid, printerid) and a job size in pages."""
-        jobprice = self.computePrinterJobPrice(printerid, pagecount)
-        queries = []    
-        queries.append("UPDATE userpquota SET lifepagecounter=lifepagecounter+(%s), pagecounter=pagecounter+(%s) WHERE userid=%s AND printerid=%s" % (self.doQuote(pagecount), self.doQuote(pagecount), self.doQuote(userid), self.doQuote(printerid)))
-        queries.append("UPDATE users SET balance=balance-(%s) WHERE id=%s" % (self.doQuote(jobprice), self.doQuote(userid)))
-        self.doQuery(queries)
-        
-    def getUserPQuota(self, userid, printerid) :
-        """Returns the Print Quota information for a given (userid, printerid)."""
-        result = self.doQuery("SELECT lifepagecounter, pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(userid), self.doQuote(printerid)))
-        try :
-            return self.doParseResult(result)[0]
-        except TypeError :      # Not found    
-            return
-        
-    def getGroupPQuota(self, groupid, printerid) :
-        """Returns the Print Quota information for a given (groupid, printerid)."""
-        result = self.doQuery("SELECT softlimit, hardlimit, datelimit FROM grouppquota WHERE groupid=%s AND printerid=%s" % (self.doQuote(groupid), self.doQuote(printerid)))
-        try :
-            grouppquota = self.doParseResult(result)[0]
-        except TypeError :    
-            return
-        else :    
-            result = self.doQuery("SELECT SUM(lifepagecounter) as lifepagecounter, SUM(pagecounter) as pagecounter FROM userpquota WHERE printerid=%s AND userid in (SELECT userid FROM groupsmembers WHERE groupid=%s)" % (self.doQuote(printerid), self.doQuote(groupid)))
-            try :
-                result = self.doParseResult(result)[0]
-            except TypeError :      # Not found    
-                return
-            else :    
-                grouppquota.update({"lifepagecounter": result["lifepagecounter"], "pagecounter": result["pagecounter"]})
-                return grouppquota
-        
-    def setUserDateLimit(self, userid, printerid, datelimit) :
-        """Sets the limit date for a soft limit to become an hard one given (userid, printerid)."""
-        self.doQuery("UPDATE userpquota SET datelimit=%s::TIMESTAMP WHERE userid=%s AND printerid=%s" % (self.doQuote("%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)), self.doQuote(userid), self.doQuote(printerid)))
-        
-    def setGroupDateLimit(self, groupid, printerid, datelimit) :
-        """Sets the limit date for a soft limit to become an hard one given (groupid, printerid)."""
-        self.doQuery("UPDATE grouppquota SET datelimit=%s::TIMESTAMP WHERE groupid=%s AND printerid=%s" % (self.doQuote("%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)), self.doQuote(groupid), self.doQuote(printerid)))
-        
-    def addJobToHistory(self, jobid, userid, printerid, pagecounter, action, jobsize=None) :
-        """Adds a job to the history: (jobid, userid, printerid, last page counter taken from requester)."""
-        self.doQuery("INSERT INTO jobhistory (jobid, userid, printerid, pagecounter, action, jobsize) VALUES (%s, %s, %s, %s, %s, %s)" % (self.doQuote(jobid), self.doQuote(userid), self.doQuote(printerid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize)))
-        return self.getJobHistoryId(jobid, userid, printerid) # in case jobid is not sufficient
-    
-    def updateJobSizeInHistory(self, historyid, jobsize) :
-        """Updates a job size in the history given the history line's id."""
-        self.doQuery("UPDATE jobhistory SET jobsize=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(historyid)))
-    
-    def getPrinterPageCounter(self, printerid) :
-        """Returns the last page counter value for a printer given its id, also returns last username, last jobid and history line id."""
-        result = self.doQuery("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printerid))
-        try :
-            return self.doParseResult(result)[0]
-        except TypeError :      # Not found
-            return
-        
-    def addUserToGroup(self, userid, groupid) :    
-        """Adds an user to a group."""
-        result = self.doQuery("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(groupid), self.doQuote(userid)))
-        try :
-            mexists = self.doParseResult(result)[0]["mexists"]
-        except TypeError :    
-            mexists = 0
-        if not mexists :    
-            self.doQuery("INSERT INTO groupsmembers (groupid, userid) VALUES (%s, %s)" % (self.doQuote(groupid), self.doQuote(userid)))
-        
-    def deleteUser(self, userid) :    
-        """Completely deletes an user from the Quota Storage."""
-        queries = []
-        queries.append("DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(userid))
-        queries.append("DELETE FROM jobhistory WHERE userid=%s" % self.doQuote(userid))
-        queries.append("DELETE FROM userpquota WHERE userid=%s" % self.doQuote(userid))
-        queries.append("DELETE FROM users WHERE id=%s" % self.doQuote(userid))
-        # TODO : What should we do if we delete the last person who used a given printer ?
-        self.doQuery(queries)
-        
-    def deleteGroup(self, groupid) :    
-        """Completely deletes an user from the Quota Storage."""
-        queries = []
-        queries.append("DELETE FROM groupsmembers WHERE groupid=%s" % self.doQuote(groupid))
-        queries.append("DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(groupid))
-        queries.append("DELETE FROM groups WHERE id=%s" % self.doQuote(groupid))
-        self.doQuery(queries)
-        
-    def computePrinterJobPrice(self, printerid, jobsize) :    
-        """Returns the price for a job on a given printer."""
-        # TODO : create a base class with things like this
-        prices = self.getPrinterPrices(printerid)
-        if prices is None :
-            perpage = perjob = 0.0
-        else :    
-            (perpage, perjob) = prices
-        return perjob + (perpage * jobsize)
Index: /kota/trunk/pykota/storages/postgresql.py
===================================================================
--- /pykota/trunk/pykota/storages/postgresql.py (revision 976)
+++  (revision )
@@ -1,126 +1,0 @@
-# PyKota
-#
-# PyKota : Print Quotas for CUPS and LPRng
-#
-# (c) 2003 Jerome Alet <alet@librelogiciel.com>
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 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, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
-#
-# $Id$
-#
-# $Log$
-# Revision 1.10  2003/04/30 13:36:40  jalet
-# Stupid accounting method was added.
-#
-# Revision 1.9  2003/04/27 08:04:15  jalet
-# LDAP storage backend's skeleton added. DOESN'T WORK.
-#
-# Revision 1.8  2003/04/23 22:13:57  jalet
-# Preliminary support for LPRng added BUT STILL UNTESTED.
-#
-# Revision 1.7  2003/04/15 11:30:57  jalet
-# More work done on money print charging.
-# Minor bugs corrected.
-# All tools now access to the storage as priviledged users, repykota excepted.
-#
-# Revision 1.6  2003/03/29 13:45:27  jalet
-# GPL paragraphs were incorrectly (from memory) copied into the sources.
-# Two README files were added.
-# Upgrade script for PostgreSQL pre 1.01 schema was added.
-#
-# Revision 1.5  2003/03/22 13:11:33  jalet
-# The port on which the Quota Storage Sever is listening can now
-# be set in the configuration file (see sample).
-# Better error handling if PygreSQL is not installed.
-# Improved documentation.
-# Version number changed to 1.02alpha
-#
-# Revision 1.4  2003/02/17 22:05:50  jalet
-# Storage backend now supports admin and user passwords (untested)
-#
-# Revision 1.3  2003/02/06 14:49:04  jalet
-# edpykota should be ok now
-#
-# Revision 1.2  2003/02/05 22:28:38  jalet
-# More robust storage
-#
-# Revision 1.1  2003/02/05 21:28:17  jalet
-# Initial import into CVS
-#
-#
-#
-
-
-from pykota.storage import PyKotaStorageError
-from pykota.storages import sql
-
-try :
-    import pg
-except ImportError :    
-    import sys
-    # TODO : to translate or not to translate ?
-    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the PygreSQL module installed correctly." % sys.version.split()[0]
-
-class Storage(sql.SQLStorage) :
-    def __init__(self, host, dbname, user, passwd) :
-        """Opens the PostgreSQL database connection."""
-        self.closed = 1
-        try :
-            (host, port) = host.split(":")
-            port = int(port)
-        except ValueError :    
-            port = -1         # Use PostgreSQL's default tcp/ip port (5432).
-        
-        try :
-            self.database = pg.connect(host=host, port=port, dbname=dbname, user=user, passwd=passwd)
-            self.closed = 0
-        except pg.error, msg :
-            raise PyKotaStorageError, msg
-            
-    def __del__(self) :        
-        """Closes the database connection."""
-        if not self.closed :
-            self.database.close()
-            self.closed = 1
-        
-    def doQuery(self, query) :
-        """Does a query."""
-        if type(query) in (type([]), type(())) :
-            query = ";".join(query)
-        query = query.strip()    
-        if not query.endswith(';') :    
-            query += ';'
-        self.database.query("BEGIN;")
-        try :
-            result = self.database.query(query)
-        except pg.error, msg :    
-            self.database.query("ROLLBACK;")
-            raise PyKotaStorageError, msg
-        else :    
-            self.database.query("COMMIT;")
-            return result
-        
-    def doQuote(self, field) :
-        """Quotes a field for use as a string in SQL queries."""
-        if type(field) in (type(0), type(0.0)) : # TODO : do something safer
-            typ = "decimal"
-        else :    
-            typ = "text"
-        return pg._quote(field, typ)
-        
-    def doParseResult(self, result) :
-        """Returns the result as a list of Python mappings."""
-        if (result is not None) and (result.ntuples() > 0) :
-            return result.dictresult()
-        
Index: /pykota/trunk/pykota/storage.py
===================================================================
--- /pykota/trunk/pykota/storage.py (revision 952)
+++ /pykota/trunk/pykota/storage.py (revision 1021)
@@ -21,4 +21,10 @@
 #
 # $Log$
+# Revision 1.13  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
 # Revision 1.12  2003/04/23 22:13:57  jalet
 # Preliminary support for LPRng added BUT STILL UNTESTED.
@@ -78,7 +84,7 @@
     __str__ = __repr__
         
-def openConnection(config, asadmin=0) :
+def openConnection(pykotatool) :
     """Returns a connection handle to the appropriate Quota Storage Database."""
-    backendinfo = config.getStorageBackend()
+    backendinfo = pykotatool.config.getStorageBackend()
     backend = backendinfo["storagebackend"]
     try :
@@ -86,4 +92,9 @@
             # don't trust user input
             raise ImportError
+        #    
+        # TODO : descending compatibility
+        # 
+        if backend == "postgresql" :
+            backend = "pgstorage"       # TODO : delete, this is for descending compatibility only
         exec "from pykota.storages import %s as storagebackend" % backend.lower()    
     except ImportError :
@@ -93,10 +104,5 @@
         database = backendinfo["storagename"]
         admin = backendinfo["storageadmin"]
-        user = backendinfo["storageuser"]
         adminpw = backendinfo["storageadminpw"]
-        userpw = backendinfo["storageuserpw"]
-        if asadmin :
-            return getattr(storagebackend, "Storage")(host, database, admin, adminpw)
-        else :    
-            return getattr(storagebackend, "Storage")(host, database, user, userpw)
+        return getattr(storagebackend, "Storage")(pykotatool, host, database, admin, adminpw)
 
Index: /pykota/trunk/pykota/config.py
===================================================================
--- /pykota/trunk/pykota/config.py (revision 1000)
+++ /pykota/trunk/pykota/config.py (revision 1021)
@@ -21,4 +21,10 @@
 #
 # $Log$
+# Revision 1.28  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
 # Revision 1.27  2003/05/27 23:00:21  jalet
 # Big rewrite of external accounting methods.
@@ -183,9 +189,7 @@
         for option in [ "storagebackend", "storageserver", \
                         "storagename", "storageadmin", \
-                        "storageuser", \
                       ] :
             backendinfo[option] = self.getGlobalOption(option)
-        for option in [ "storageadminpw", "storageuserpw" ] :    
-            backendinfo[option] = self.getGlobalOption(option, ignore=1)
+        backendinfo["storageadminpw"] = self.getGlobalOption("storageadminpw", ignore=1)
         return backendinfo
         
@@ -304,2 +308,10 @@
         except ValueError :    
             raise PyKotaConfigError, _("Invalid grace delay %s") % gd
+            
+    def getDebug(self) :          
+        """Returns 1 if debugging is activated, else 0."""
+        debug = self.getGlobalOption("debug", ignore=1)
+        if (debug is not None) and (debug.upper().strip() in ['Y', 'YES', '1', 'ON', 'O']) :
+            return 1
+        else :    
+            return 0
Index: /pykota/trunk/pykota/version.py
===================================================================
--- /pykota/trunk/pykota/version.py (revision 1018)
+++ /pykota/trunk/pykota/version.py (revision 1021)
@@ -21,5 +21,5 @@
 #
 
-__version__ = "1.08alpha7_unofficial"
+__version__ = "1.08alpha8_unofficial"
 
 __doc__ = """PyKota : a complete Printing Quota Solution for CUPS and LPRng."""
Index: /pykota/trunk/bin/repykota
===================================================================
--- /pykota/trunk/bin/repykota (revision 1017)
+++ /pykota/trunk/bin/repykota (revision 1021)
@@ -23,4 +23,10 @@
 #
 # $Log$
+# Revision 1.37  2003/06/10 16:37:54  jalet
+# Deletion of the second user which is not needed anymore.
+# Added a debug configuration field in /etc/pykota.conf
+# All queries can now be sent to the logger in debug mode, this will
+# greatly help improve performance when time for this will come.
+#
 # Revision 1.36  2003/06/06 14:21:08  jalet
 # New LDAP schema.
@@ -291,5 +297,5 @@
         
         # Initializes the command line tool
-        reporter = RePyKota(asadmin=0, doc=__doc__)
+        reporter = RePyKota(doc=__doc__)
         
         # parse and checks the command line
Index: /pykota/trunk/initscripts/postgresql/pykota-postgresql.sql
===================================================================
--- /pykota/trunk/initscripts/postgresql/pykota-postgresql.sql (revision 1015)
+++ /pykota/trunk/initscripts/postgresql/pykota-postgresql.sql (revision 1021)
@@ -20,4 +20,10 @@
 --
 -- $Log$
+-- Revision 1.2  2003/06/10 16:37:54  jalet
+-- Deletion of the second user which is not needed anymore.
+-- Added a debug configuration field in /etc/pykota.conf
+-- All queries can now be sent to the logger in debug mode, this will
+-- greatly help improve performance when time for this will come.
+--
 -- Revision 1.1  2003/06/05 07:12:31  jalet
 -- Reorganization of directories
@@ -41,5 +47,4 @@
 -- Create the print quota database users
 -- 
-CREATE USER pykotauser;
 CREATE USER pykotaadmin;
 
@@ -120,7 +125,3 @@
 GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES ON users, groups, printers, userpquota, grouppquota, groupsmembers, jobhistory TO pykotaadmin;
 GRANT SELECT, UPDATE ON users_id_seq, groups_id_seq, printers_id_seq, userpquota_id_seq, grouppquota_id_seq, jobhistory_id_seq TO pykotaadmin;
-GRANT SELECT, UPDATE ON printers, userpquota, grouppquota TO pykotauser;
-GRANT SELECT ON users, groups, groupsmembers TO pykotauser;
-GRANT SELECT, INSERT, UPDATE ON jobhistory TO pykotauser;
-GRANT SELECT, UPDATE ON jobhistory_id_seq TO pykotauser;
 
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 1019)
+++ /pykota/trunk/NEWS (revision 1021)
@@ -22,4 +22,12 @@
 PyKota NEWS :
 
+    - 1.08alpha8 :
+    
+        - Code refactoring.
+        - A single user/password pair is used to connect
+          to the database backend. The storageuser configuration
+          field, and its associated passwords storageuserpw, are 
+          not used anymore.
+        
     - 1.08alpha7 :
     
