Index: pykota/trunk/conf/pykota.conf.sample
===================================================================
--- pykota/trunk/conf/pykota.conf.sample (revision 695)
+++ pykota/trunk/conf/pykota.conf.sample (revision 695)
@@ -0,0 +1,64 @@
+# PyKota sample configuration file
+#
+# Copy this file in cups' configuration directory
+# usually /etc/cups under the name pykota.conf
+#
+# PyKota - Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+
+[global]
+# Storage backend for quotas
+# only PostgreSQL is supported
+# Ldap, MySQL, Berkeley are planned
+storagebackend: postgresql
+storageserver: localhost
+storagename: pykota
+storageadmin: pykotaadmin
+storageuser: pykotauser
+# storageadminpw: NOT YET SUPPORTED
+# storageuserpw: NOT YET SUPPORTED
+
+# Where to log ?
+# supported values : stderr, syslog
+logger: stderr
+
+# Print Quota administrator
+admin: Jerome Alet
+adminmail: alet@unice.fr
+#
+# Mail server to use to warn users
+smtpserver: localhost
+#
+# Accounting method
+# Only LAZY is currently supported, see documentation
+method: lazy
+#
+# Grace delay in days
+gracedelay: 7
+
+# one section per printer
+[lp]
+#
+# How to query the lp printer for its page counter
+# Only SNMP is currently supported
+requester: snmp
+# SNMP Community
+snmpcmnty: public
+# SNMP oid to query
+snmpoid: 43.10.2.1.4.1.1
+#
+# Default policy for inexistant users (e.g. root)
+# either accept or deny
+policy: accept
+
Index: pykota/trunk/pykota/loggers/__init__.py
===================================================================
--- pykota/trunk/pykota/loggers/__init__.py (revision 695)
+++ pykota/trunk/pykota/loggers/__init__.py (revision 695)
@@ -0,0 +1,15 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
Index: pykota/trunk/pykota/loggers/syslog.py
===================================================================
--- pykota/trunk/pykota/loggers/syslog.py (revision 695)
+++ pykota/trunk/pykota/loggers/syslog.py (revision 695)
@@ -0,0 +1,43 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+import syslog
+
+class Logger :
+    """A logger class which logs to syslog."""
+    def __init__(self) :
+        """Opens the logging subsystem."""
+        syslog.openlog("PyKota", 0, syslog.LOG_LPR)
+        
+    def __del__(self) :    
+        """Ensures the logging subsystem is closed."""
+        syslog.closelog()
+        
+    def log_message(self, message, level) :
+        """Sends the message to syslog."""
+        try :
+            priority = getattr(syslog, "LOG_%s" % level.upper())
+        except AttributeError :    
+            # Bad priority name, we log this as a DEBUG message
+            priority = syslog.LOG_DEBUG
+        syslog.syslog(priority, message)
Index: pykota/trunk/pykota/loggers/stderr.py
===================================================================
--- pykota/trunk/pykota/loggers/stderr.py (revision 695)
+++ pykota/trunk/pykota/loggers/stderr.py (revision 695)
@@ -0,0 +1,30 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+
+class Logger :
+    """A logger class which logs to stderr."""
+    def log_message(self, message, level="info") :
+        """Sends the message to the appropriate logging subsystem."""
+        sys.stderr.write("%s: PyKota : %s\n" % (level.upper(), message.strip()))
+        sys.stderr.flush()
Index: pykota/trunk/pykota/requesters/snmp.py
===================================================================
--- pykota/trunk/pykota/requesters/snmp.py (revision 695)
+++ pykota/trunk/pykota/requesters/snmp.py (revision 695)
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+
+# PyKota accounting filter
+#
+# PyKota - Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+from pykota.requester import PyKotaRequesterError
+
+class Requester :
+    """A class to send queries to printers via SNMP."""
+    def __init__(self, config, printername) :
+        """Sets instance vars depending on the current printer."""
+        self.printername = printername
+        self.community = config.config.get(printername, "snmpcmnty")
+        self.oid = config.config.get(printername, "snmpoid")
+        
+    def getPrinterPageCounter(self, hostname) :
+        """Returns the page counter from the hostname printer via SNMP.
+        
+           Currently uses the snmpget external command. TODO : do it internally 
+        """
+        if hostname is None :
+            raise PyKotaRequesterError, "Unknown printer address in SNMP(%s, %s) for printer %s" % (self.community, self.oid, self.printername)
+        answer = os.popen("snmpget -c %s -Ov %s %s" % (self.community, hostname, self.oid))
+        try :
+            pagecounter = int(answer.readline().split()[-1].strip())
+        except IndexError :    
+            raise PyKotaRequesterError, "Unable to query printer %s via SNMP(%s, %s)" % (hostname, self.community, self.oid) 
+        answer.close()
+        return pagecounter
+    
Index: pykota/trunk/pykota/requesters/__init__.py
===================================================================
--- pykota/trunk/pykota/requesters/__init__.py (revision 695)
+++ pykota/trunk/pykota/requesters/__init__.py (revision 695)
@@ -0,0 +1,15 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
Index: pykota/trunk/pykota/tool.py
===================================================================
--- pykota/trunk/pykota/tool.py (revision 695)
+++ pykota/trunk/pykota/tool.py (revision 695)
@@ -0,0 +1,96 @@
+#! /usr/bin/env python
+
+# PyKota - Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+import os
+import smtplib
+
+from pykota import config
+from pykota import storage
+from pykota import logger
+
+class PyKotaToolError(Exception):
+    """An exception for PyKota config related stuff."""
+    def __init__(self, message = ""):
+        self.message = message
+        Exception.__init__(self, message)
+    def __repr__(self):
+        return self.message
+    __str__ = __repr__
+    
+class PyKotaTool :    
+    """Base class for all PyKota command line tools."""
+    def __init__(self, isfilter=0) :
+        """Initializes the command line tool."""
+        self.config = config.PyKotaConfig(os.environ.get("CUPS_SERVERROOT", "/etc/cups"))
+        self.logger = logger.Logger(self.config)
+        self.storage = storage.openConnection(self.config, asadmin=(not isfilter))
+        self.printername = os.environ.get("PRINTER", None)
+        self.printerhostname = self.getPrinterHostname()
+        self.smtpserver = self.config.getSMTPServer()
+        self.admin = self.config.getAdmin()
+        self.adminmail = self.config.getAdminMail()
+        
+    def sendMessage(self, touser, fullmessage) :
+        """Sends an email message containing headers to some user."""
+        fullmessage += "\n\nPlease contact your system administrator %s - <%s>\n" % (self.admin, self.adminmail)
+        if "@" not in touser :
+            touser = "%s@%s" % (touser, self.smtpserver)
+        server = smtplib.SMTP(self.smtpserver)
+        server.sendmail(self.adminmail, [touser], fullmessage)
+        server.quit()
+        
+    def sendMessageToUser(self, username, subject, message) :
+        """Sends an email message to a user."""
+        self.sendMessage(username, "Subject: %s\n\n%s" % (subject, message))
+        
+    def sendMessageToAdmin(self, subject, message) :
+        """Sends an email message to the Print Quota administrator."""
+        self.sendMessage(self.adminmail, "Subject: %s\n\n%s" % (subject, message))
+        
+    def getPrinterHostname(self) :
+        """Returns the printer hostname."""
+        device_uri = os.environ.get("DEVICE_URI", "")
+        # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
+        try :
+            (backend, destination) = device_uri.split(":", 1) 
+        except ValueError :    
+            raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
+        while destination.startswith("/") :
+            destination = destination[1:]
+        return destination.split("/")[0].split(":")[0]
+        
+    def warnQuotaPrinter(self, username) :
+        """Checks a user quota and send him a message if quota is exceeded on current printer."""
+        (action, grace, gracedate) = self.storage.checkUserPQuota(username, self.printername)
+        if action == "DENY" :
+            adminmessage = "Print Quota exceeded for user %s on printer %s" % (username, self.printername)
+            self.logger.log_message(adminmessage)
+            self.sendMessageToUser(username, "Print Quota Exceeded", "You are not allowed to print anymore because your Print Quota is exceeded.")
+            self.sendMessageToAdmin("Print Quota", adminmessage)
+        elif action == "WARN" :    
+            adminmessage = "Print Quota soft limit exceeded for user %s on printer %s" % (username, self.printername)
+            self.logger.log_message(adminmessage)
+            self.sendMessageToUser(username, "Print Quota Exceeded", "You will soon be forbidden to print anymore because your Print Quota is almost reached.")
+            self.sendMessageToAdmin("Print Quota", adminmessage)
+        return action        
+    
Index: pykota/trunk/pykota/__init__.py
===================================================================
--- pykota/trunk/pykota/__init__.py (revision 695)
+++ pykota/trunk/pykota/__init__.py (revision 695)
@@ -0,0 +1,15 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
Index: pykota/trunk/pykota/logger.py
===================================================================
--- pykota/trunk/pykota/logger.py (revision 695)
+++ pykota/trunk/pykota/logger.py (revision 695)
@@ -0,0 +1,42 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+
+class PyKotaLoggingError(Exception):
+    """An exception for logging related stuff."""
+    def __init__(self, message = ""):
+        self.message = message
+        Exception.__init__(self, message)
+    def __repr__(self):
+        return self.message
+    __str__ = __repr__
+
+def openLogger(config) :
+    """Returns the appropriate logger subsystem object."""
+    backend = config.getLoggingBackend()
+    try :
+        module = __import__("pykota.loggers." + backend.lower())
+    except ImportError :
+        raise PyKotaLoggingError, "Unsupported logging subsystem %s" % backend
+    else :    
+        return getattr(module, "Logger")()
Index: pykota/trunk/pykota/requester.py
===================================================================
--- pykota/trunk/pykota/requester.py (revision 695)
+++ pykota/trunk/pykota/requester.py (revision 695)
@@ -0,0 +1,40 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+class PyKotaRequesterError(Exception):
+    """An exception for Requester related stuff."""
+    def __init__(self, message = ""):
+        self.message = message
+        Exception.__init__(self, message)
+    def __repr__(self):
+        return self.message
+    __str__ = __repr__
+    
+def openRequester(config, printername) :
+    """Returns a connection handle to the appropriate requester."""
+    backend = config.getRequesterBackend(printername)
+    try :
+        module = __import__("pykota.requesters." + backend.lower())
+    except ImportError :
+        raise PyKotaRequesterError, "Unsupported requester backend %s" % backend
+    else :    
+        return getattr(module, "Requester")(config, printername)
Index: pykota/trunk/pykota/storages/__init__.py
===================================================================
--- pykota/trunk/pykota/storages/__init__.py (revision 695)
+++ pykota/trunk/pykota/storages/__init__.py (revision 695)
@@ -0,0 +1,15 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
Index: pykota/trunk/pykota/storages/sql.py
===================================================================
--- pykota/trunk/pykota/storages/sql.py (revision 695)
+++ pykota/trunk/pykota/storages/sql.py (revision 695)
@@ -0,0 +1,97 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+from mx import DateTime
+
+class SQLStorage :    
+    def getUserId(self, username) :
+        result = self.doQuery("SELECT id FROM users WHERE username=%s;" % self.doQuote(username))
+        return self.doParseResult(result)["id"]
+            
+    def getPrinterId(self, printername) :        
+        result = self.doQuery("SELECT id FROM printers WHERE printername=%s;" % self.doQuote(printername))
+        return self.doParseResult(result)["id"]
+            
+    def getPrinterPageCounter(self, printername) :
+        result = self.doQuery("SELECT pagecounter, lastusername FROM printers WHERE printername=%s;" % self.doQuote(printername))
+        return self.doParseResult(result)
+        
+    def updatePrinterPageCounter(self, printername, username, pagecount) :
+        return self.doQuery("UPDATE printers SET pagecounter=%s, lastusername=%s WHERE printername=%s;" % (self.doQuote(pagecount), self.doQuote(username), self.doQuote(printername)))
+        
+    def addUserPQuota(self, username, printername) :
+        printerid = self.getPrinterId(printername)
+        if printerid is None :    
+            self.doQuery("INSERT INTO printers (printername) VALUES (%s);" % self.doQuote(printername))
+            printerid = self.getPrinterId(printername)
+        userid = self.getUserId(username)
+        if userid is None :    
+            self.doQuery("INSERT INTO users (username) VALUES (%s);" % self.doQuote(username))
+            userid = self.getUserId(username)
+        if (printerid is not None) and (userid is not None) :    
+            return self.doQuery("INSERT INTO userpquota (userid, printerid) VALUES (%s, %s);" % (self.doQuote(userid), self.doQuote(printerid)))
+        
+    def getUserPQuota(self, username, printername) :
+        result = self.doQuery("SELECT pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s;" % (self.doQuote(self.getUserId(username)), self.doQuote(self.getPrinterId(printername))))
+        return self.doParseResult(result)
+        
+    def setUserPQuota(self, username, printername, softlimit, hardlimit) :
+        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(self.getUserId(username)), self.doQuote(self.getPrinterId(printername))))
+        
+    def updateUserPQuota(self, username, printername, pagecount) :
+        self.doQuery("UPDATE userpquota SET pagecounter=pagecounter+(%s) WHERE userid=%s AND printerid=%s;" % (self.doQuote(pagecount), self.doQuote(self.getUserId(username)), self.doQuote(self.getPrinterId(printername))))
+        
+    def buyUserPQuota(self, username, printername, pagebought) :
+        self.updateUserPQuota(username, printername, -pagebought)
+        
+    def checkUserPQuota(self, username, printername) :
+        # TODO : this doesn't work as expected wrt dates
+        # TODO : GRACEDELAY should come from the configuration file
+        # TODO : move this into the PyKotaTool class
+        now = DateTime.now()
+        quota = self.getUserPQuota(username, printername)
+        pagecounter = quota["pagecounter"]
+        softlimit = quota["softlimit"]
+        hardlimit = quota["hardlimit"]
+        datelimit = quota["datelimit"]
+        if datelimit :
+            datelimit = DateTime.DateTime(datelimit)    # TODO : check this !
+        if softlimit is not None :
+            if pagecounter < softlimit :
+                action = "ALLOW"
+            elif hardlimit is not None :
+                 if softlimit <= pagecounter < hardlimit :    
+                     if datelimit is None :
+                         self.doQuery("UPDATE userpquota SET datelimit=%s WHERE userid=%s AND printerid=%s;" % ('%04i-%02i-%02i' % (now.year, now.month, now.day), self.doQuote(self.getUserId(username)), self.doQuote(self.getPrinterId(printername))))
+                         datelimit = now
+                     if (now - datelimit) <= GRACEDELAY :
+                         action = "WARN"
+                     else :    
+                         action = "DENY"
+                 else :         
+                     action = "DENY"
+            else :        
+                action = "DENY"
+        else :        
+            action = "ALLOW"
+        return (action, (hardlimit - pagecounter), datelimit)
+    
Index: pykota/trunk/pykota/storages/postgresql.py
===================================================================
--- pykota/trunk/pykota/storages/postgresql.py (revision 695)
+++ pykota/trunk/pykota/storages/postgresql.py (revision 695)
@@ -0,0 +1,65 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import pg
+
+from pykota.storage import PyKotaStorageError
+from pykota.storages import sql
+
+class Storage(sql.SQLStorage) :
+    def __init__(self, host, dbname, user) :
+        """Opens the PostgreSQL database connection."""
+        self.closed = 1
+        try :
+            self.database = pg.connect(host=host, dbname=dbname, user=user)
+            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."""
+        try :
+            return self.database.query(query)
+        except pg.error, msg :    
+            raise PyKotaStorageError, msg
+        
+    def doQuote(self, field) :
+        """Quotes a field for use as a string in SQL queries."""
+        if type(field) == type(0) : # TODO : do something safer
+            typ = "decimal"
+        else :    
+            typ = "text"
+        return pg._quote(field, typ)
+        
+    def doParseResult(self, result) :
+        """Returns the result as a Python dictionnary."""
+        try :
+            return result.dictresult()[0]
+        except IndexError :    
+            return None # not found
+        
Index: pykota/trunk/pykota/storage.py
===================================================================
--- pykota/trunk/pykota/storage.py (revision 695)
+++ pykota/trunk/pykota/storage.py (revision 695)
@@ -0,0 +1,40 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+class PyKotaStorageError(Exception):
+    """An exception for Quota Storage related stuff."""
+    def __init__(self, message = ""):
+        self.message = message
+        Exception.__init__(self, message)
+    def __repr__(self):
+        return self.message
+    __str__ = __repr__
+    
+def openConnection(config, asadmin=0) :
+    """Returns a connection handle to the appropriate Quota Storage Database."""
+    (backend, host, database, admin, user) = config.getStorageBackend()
+    try :
+        module = __import__("pykota.storages." + backend.lower())
+    except ImportError :
+        raise PyKotaStorageError, "Unsupported quota storage backend %s" % backend
+    else :    
+        return getattr(module, "Storage")(host, database, (asadmin and admin) or user)
Index: pykota/trunk/pykota/config.py
===================================================================
--- pykota/trunk/pykota/config.py (revision 695)
+++ pykota/trunk/pykota/config.py (revision 695)
@@ -0,0 +1,127 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+import os
+import ConfigParser
+
+class PyKotaConfigError(Exception):
+    """An exception for PyKota config related stuff."""
+    def __init__(self, message = ""):
+        self.message = message
+        Exception.__init__(self, message)
+    def __repr__(self):
+        return self.message
+    __str__ = __repr__
+    
+class PyKotaConfig :
+    """A class to deal with PyKota's configuration."""
+    def __init__(self, directory) :
+        """Reads and checks the configuration file."""
+        self.filename = os.path.join(directory, "pykota.conf")
+        self.config = ConfigParser.ConfigParser()
+        self.config.read([self.filename])
+        self.checkConfiguration()
+        
+    def checkConfiguration(self) :
+        """Checks if configuration is correct.
+        
+           raises PyKotaConfigError in case a problem is detected
+        """
+        for option in [ "storagebackend", "storageserver", \
+                        "storagename", "storageadmin", \
+                        "storageuser", # TODO : "storageadminpw", "storageusepw", \
+                        "logger", "admin", "adminmail",
+                        "smtpserver", "method", "gracedelay" ] :
+            if not self.config.has_option("global", option) :            
+                raise PyKotaConfigError, "Option %s not found in section global of %s" % (option, self.filename)
+                
+        # more precise checks        
+        validloggers = [ "stderr", "syslog" ] 
+        if self.config.get("global", "logger").lower() not in validloggers :             
+            raise PyKotaConfigError, "Option logger only supports values in %s" % str(validloggers)
+            
+        validmethods = [ "lazy" ] # TODO add more methods            
+        if self.config.get("global", "method").lower() not in validmethods :             
+            raise PyKotaConfigError, "Option method only supports values in %s" % str(validmethods)
+            
+        # check all printers now 
+        for printer in self.getPrinterNames() :
+            for poption in [ "requester", "policy" ] : 
+                if not self.config.has_option(printer, poption) :
+                    raise PyKotaConfigError, "Option %s not found in section %s of %s" % (option, printer, self.filename)
+                    
+            validpolicies = [ "accept", "deny" ]     
+            if self.config.get(printer, "policy").lower() not in validpolicies :
+                raise PyKotaConfigError, "Option policy in section %s only supports values in %s" % (printer, str(validrequesters))
+            
+            validrequesters = [ "snmp" ] # TODO : add more requesters
+            requester = self.config.get(printer, "requester").lower()
+            if requester not in validrequesters :
+                raise PyKotaConfigError, "Option requester in section %s only supports values in %s" % (printer, str(validrequesters))
+            if requester == "snmp" :
+                for poption in [ "snmpcmnty", "snmpoid" ] : 
+                    if not self.config.has_option(printer, poption) :
+                        raise PyKotaConfigError, "Option %s not found in section %s of %s" % (option, printer, self.filename)
+                        
+    def getPrinterNames(self) :    
+        """Returns the list of configured printers, i.e. all sections names minus 'global'."""
+        return [pname for pname in self.config.sections() if pname != "global"]
+        
+    def getStorageBackend(self) :    
+        """Returns the storage backend information as a tuple.
+        
+           The tuple has the form :
+           
+             (backend, host, database, admin, user)
+        """        
+        backendinfo = []
+        for option in [ "storagebackend", "storageserver", \
+                        "storagename", "storageadmin", \
+                        "storageuser", # TODO : "storageadminpw", "storageusepw", \
+                      ] :
+            backendinfo.append(self.config.get("global", option))
+        return tuple(backendinfo)    
+        
+    def getLoggingBackend(self) :    
+        """Returns the logging backend information."""
+        return self.config.get("global", "logger").lower()
+        
+    def getRequesterBackend(self, printer) :    
+        """Returns the requester backend to use for a given printer."""
+        return self.config.get(printer, "requester").lower()
+        
+    def getPrinterPolicy(self, printer) :    
+        """Returns the default policy for the current printer."""
+        return self.config.get(printer, "policy").lower()
+        
+    def getSMTPServer(self) :    
+        """Returns the SMTP server to use to send messages to users."""
+        return self.config.get("global", "smtpserver").lower()
+        
+    def getAdminMail(self) :    
+        """Returns the Email address of the Print Quota Administrator."""
+        return self.config.get("global", "adminmail")
+        
+    def getAdmin(self) :    
+        """Returns the full name of the Print Quota Administrator."""
+        return self.config.get("global", "admin")
Index: pykota/trunk/pykota/version.py
===================================================================
--- pykota/trunk/pykota/version.py (revision 695)
+++ pykota/trunk/pykota/version.py (revision 695)
@@ -0,0 +1,19 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+
+__version__ = "0.1-unofficial"
+
+__doc__ = """PyKota : a complete Printing Quota Solution for CUPS."""
Index: pykota/trunk/setup.py
===================================================================
--- pykota/trunk/setup.py (revision 695)
+++ pykota/trunk/setup.py (revision 695)
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+#
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+import glob
+import os
+from distutils.core import setup
+
+sys.path.insert(0, "pykota")
+from pykota.version import __version__, __doc__
+
+data_files = []
+mofiles = glob.glob(os.sep.join(["po", "*", "*.mo"]))
+for mofile in mofiles :
+    lang = mofile.split(os.sep)[1]
+    directory = os.sep.join(["share", "locale", lang, "LC_MESSAGES"])
+    data_files.append((directory, [ mofile ]))
+
+setup(name = "pykota", version = __version__,
+      license = "GNU GPL",
+      description = __doc__,
+      author = "Jerome Alet",
+      author_email = "alet@librelogiciel.com",
+      url = "http://www.librelogiciel.com/software/",
+      packages = [ "pykota", "pykota.storages", "pykota.requesters", "pykota.loggers" ],
+      scripts = [ "bin/pykota", "bin/edpykota", "bin/repykota", "bin/warnpykota" ],
+      data_files = data_files)
+
Index: pykota/trunk/COPYING
===================================================================
--- pykota/trunk/COPYING (revision 695)
+++ pykota/trunk/COPYING (revision 695)
@@ -0,0 +1,340 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          59 Temple Place, Suite 330
+                          Boston, MA 02111-1307, USA.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+        Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: pykota/trunk/bin/pykota
===================================================================
--- pykota/trunk/bin/pykota (revision 695)
+++ pykota/trunk/bin/pykota (revision 695)
@@ -0,0 +1,113 @@
+#! /usr/bin/env python
+
+# PyKota accounting filter
+#
+# PyKota - Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
+import sys
+
+from pykota.tool import PyKotaTool, PyKotaToolError
+from pykota import requester
+
+class PyKotaFiler(PyKotaTool) :    
+    """Class for the PyKota filter."""
+    def __init__(self, username)
+        PyKotaTool.__init__(self, isfilter=1)
+        self.username = username
+        self.requester = requester.openRequester(self.config, self.printername)
+    
+    def filterInput(self, inputfile) :
+        """Transparent filter."""
+        mustclose = 0    
+        if inputfile is not None :    
+            infile = open(inputfile, "rb")
+            mustclose = 1
+        else :    
+            infile = sys.stdin
+        data = infile.read(65536)    
+        while data :
+            sys.stdout.write(data)
+            data = infile.read(65536)
+        if mustclose :    
+            infile.close()
+            
+def main() :    
+    """Do it, and do it right !"""
+    #
+    # This is a CUPS filter, so we should act and die like a CUPS filter when needed
+    narg = len(sys.argv)
+    if narg not in (6, 7) :    
+        sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
+        return 1
+    elif narg == 7 :    
+        # input file
+        inputfile = sys.argv[6]
+    else :    
+        # stdin
+        inputfile = None
+        
+    #    
+    # According to CUPS documentation, the username is the third command line argument
+    username = sys.argv[2].strip()    
+    
+    # Initializes the current tool
+    tool = PyKotaFilter(username)    
+    
+    # Get the page counter directly from the printer itself
+    counterbeforejob = tool.requester.getPrinterPageCounter(tool.printerhostname) # TODO use printername instead, make them match from CUPS' config files
+    
+    # Get the last page counter and last username from the Quota Storage backend
+    pgc = tool.storage.getPrinterPageCounter(tool.printername)    
+    if pgc is None :
+        # The printer is unknown from the Quota Storage perspective
+        # we let the job pass through, but log a warning message
+        tool.logger.log_message("Printer %s not registered in the PyKota system" % tool.printername, "warn")
+    else :    
+        (lastpagecounter, lastusername) = (pgc["pagecounter"], pgc["lastusername"])
+        
+        # Update the last page counter and last username in the Quota Storage backend
+        # set them to current user and 
+        tool.storage.updatePrinterPageCounter(tool.printername, username, counterbeforejob) # TODO : allow or deny users not in quota system, and die cleanly if needed
+        
+        # Is the current user allowed to print at all ?
+        action = tool.warnQuotaPrinter(username)
+        if action == "DENY" :
+            # No, just die cleanly
+            return 1
+            
+        # Yes     
+        if (lastpagecounter is None) or (lastusername is None) :
+            lastusername = username
+            lastpagecounter = counterbeforejob
+        jobsize = (counterbeforejob - lastpagecounter)    
+        if jobsize >= 0:
+            tool.storage.updateUserPQuota(lastusername, tool.printername, jobsize)
+            tool.warnQuotaPrinter(lastusername)
+        else :    
+            tool.logger.log_message("Error in page count value %i for user %s on printer %s" % (jobsize, tool.printername, lastusername), "error")
+        
+    # pass the job untouched to the underlying layer
+    tool.filterInput(inputfile)      
+    
+    return 0
+
+if __name__ == "__main__" :    
+    sys.exit(main() or 0)
Index: pykota/trunk/MANIFEST.in
===================================================================
--- pykota/trunk/MANIFEST.in (revision 695)
+++ pykota/trunk/MANIFEST.in (revision 695)
@@ -0,0 +1,5 @@
+include COPYING TODO NEWS CREDITS MANIFEST.in bin/pykota bin/edpykota bin/warnpykota bin/repykota
+recursive-include po *.po *.mo *.pot
+recursive-include docs *.xml
+recursive-include conf README *.sample
+recursive-include initscripts README README.* pykota-*
Index: pykota/trunk/initscripts/README.postgresql
===================================================================
--- pykota/trunk/initscripts/README.postgresql (revision 695)
+++ pykota/trunk/initscripts/README.postgresql (revision 695)
@@ -0,0 +1,36 @@
+# $Id$
+
+PyKota - Print Quota for CUPS
+
+(c) 2003 Jerome Alet <alet@librelogiciel.com>
+You're welcome to redistribute this software under the
+terms of the GNU General Public Licence version 2.0
+or, at your option, any higher version.
+
+You can read the complete GNU GPL in the file COPYING
+which should come along with this software, or visit
+the Free Software Foundation's WEB site http://www.fsf.org
+
+============================================================
+
+Documentation for the pykota-postgresql.sql script :
+----------------------------------------------------
+
+        - Launch the psql frontend and connect to the
+          database template1 as a PostgreSQL administrator
+          (i.e. the postgres user) :
+          
+            $ psql -h localhost -U postgres template1
+          
+        - Executes the pykota-postgresql.sql script :  
+        
+            template1=# \i pykota-postgresql.sql
+            
+        - Quit the psql frontend :    
+        
+            template1=# \q
+            
+        - Your PostgreSQL PyKota storage is now ready
+          for use.
+        
+============================================================
Index: pykota/trunk/initscripts/pykota-postgresql.sql
===================================================================
--- pykota/trunk/initscripts/pykota-postgresql.sql (revision 695)
+++ pykota/trunk/initscripts/pykota-postgresql.sql (revision 695)
@@ -0,0 +1,95 @@
+--
+-- PyKota - Print Quotas for CUPS
+--
+-- (c) 2003 Jerome Alet <alet@librelogiciel.com>
+-- You're welcome to redistribute this software under the
+-- terms of the GNU General Public Licence version 2.0
+-- or, at your option, any higher version.
+--
+-- You can read the complete GNU GPL in the file COPYING
+-- which should come along with this software, or visit
+-- the Free Software Foundation's WEB site http://www.fsf.org
+--
+-- $Id$
+--
+-- $Log$
+-- Revision 1.1  2003/02/05 21:28:17  jalet
+-- Initial import into CVS
+--
+--
+--
+
+--
+-- PyKota Database creation script for PostgreSQL
+--
+-- Launch this as PostgreSQL administrator with \i
+--
+
+
+--
+-- Create the print quota database
+--
+CREATE DATABASE pykota;
+
+--
+-- Create the print quota database users
+-- 
+CREATE USER pykotauser;
+CREATE USER pykotaadmin;
+
+-- 
+-- Now connect to the new database
+-- 
+\connect pykota
+
+--
+-- Create the users table
+--
+CREATE TABLE users(id SERIAL PRIMARY KEY NOT NULL,
+                   username TEXT UNIQUE NOT NULL);
+                   
+--
+-- Create the groups table
+--
+CREATE TABLE groups(id SERIAL PRIMARY KEY NOT NULL,
+                    groupname TEXT UNIQUE NOT NULL);
+                    
+--
+-- Create the printers table
+--
+CREATE TABLE printers(id SERIAL PRIMARY KEY NOT NULL,
+                      printername TEXT UNIQUE NOT NULL,
+                      lastusername TEXT,
+                      pagecounter INT4);
+                    
+--
+-- Create the print quota table for users
+--
+CREATE TABLE userpquota(id SERIAL PRIMARY KEY NOT NULL,
+                        userid INT4 REFERENCES users(id),
+                        printerid INT4 REFERENCES printers(id),
+                        pagecounter INT4 DEFAULT 0,
+                        softlimit INT4,
+                        hardlimit INT4,
+                        datelimit DATETIME);
+                        
+--
+-- Create the print quota table for groups
+--
+CREATE TABLE grouppquota(id SERIAL PRIMARY KEY NOT NULL,
+                         groupid INT4 REFERENCES groups(id),
+                         printerid INT4 REFERENCES printers(id),
+                         pagecounter INT4 DEFAULT 0,
+                         softlimit INT4,
+                         hardlimit INT4,
+                         datelimit DATETIME);
+                        
+--                        
+-- Set some ACLs                        
+--
+REVOKE ALL ON users, groups, printers, userpquota, grouppquota FROM public;                        
+GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES ON users, groups, printers, userpquota, grouppquota TO pykotaadmin;
+GRANT SELECT, UPDATE ON users_id_seq, groups_id_seq, printers_id_seq, userpquota_id_seq, grouppquota_id_seq TO pykotaadmin;
+GRANT SELECT, UPDATE ON printers, userpquota, grouppquota TO pykotauser;
+GRANT SELECT ON users, groups TO pykotauser;
+
Index: pykota/trunk/initscripts/README
===================================================================
--- pykota/trunk/initscripts/README (revision 695)
+++ pykota/trunk/initscripts/README (revision 695)
@@ -0,0 +1,27 @@
+# $Id$
+
+PyKota - Print Quota for CUPS
+
+(c) 2003 Jerome Alet <alet@librelogiciel.com>
+You're welcome to redistribute this software under the
+terms of the GNU General Public Licence version 2.0
+or, at your option, any higher version.
+
+You can read the complete GNU GPL in the file COPYING
+which should come along with this software, or visit
+the Free Software Foundation's WEB site http://www.fsf.org
+
+============================================================
+
+This directory contains scripts to initialise the PyKota
+storage database. Use the one which best suits your need
+depending on the storage backend you want to use. Each
+script has an associated README file, be sure to read 
+this as well :
+
+        File                            Backend
+        --------------------------------------------------
+        pykota-posgresql.sql      |     PostgreSQL storage
+                                  |
+        
+============================================================
Index: pykota/trunk/README
===================================================================
--- pykota/trunk/README (revision 695)
+++ pykota/trunk/README (revision 695)
@@ -0,0 +1,100 @@
+# $Id$
+
+PyKota - Print Quota for CUPS
+
+(c) 2003 Jerome Alet <alet@librelogiciel.com>
+You're welcome to redistribute this software under the
+terms of the GNU General Public Licence version 2.0
+or, at your option, any higher version.
+
+You can read the complete GNU GPL in the file COPYING
+which should come along with this software, or visit
+the Free Software Foundation's WEB site http://www.fsf.org
+
+============================================================
+
+PyKota is a complete Print Quota system for the Common Unix Printing
+System (aka CUPS), which works by directly querying the printers
+for the number of pages they have printed.
+
+Current or planned features are :
+
+        - Easy installation.
+        
+        - Per printer User and Group quotas. Currently Group
+          quotas are only at skeleton stage.
+        
+        - Centralized configurable storage : currently
+          only a PostgreSQL database backend is supported,
+          but at least LDAP and MySQL are planned.
+          
+        - Automated mailing of warning or error messages to  
+          users and print administrator when print quota is 
+          exceeded.
+          
+        - Configurable printer querying methods. Currently
+          only SNMP method is supported.
+
+Look at the documentation in the docs subdirectory to learn
+more.
+
+============================================================
+
+INSTALLATION:
+=============
+
+Download the latest PyKota version from :
+
+        http://www.librelogiciel.com/software/
+
+Extract it:
+
+        gzip -d pykota-x.xx.tar.gz | tar -xf -
+
+        where x.xx is PyKota's latest version number.
+
+Go to PyKota's directory:
+
+        cd pykota-x.xx
+
+Just type:
+
+        python setup.py install
+
+You may need to be logged in with sufficient privileges (e.g. root)
+
+Go to the initscripts subdirectory of PyKota's sources, and choose
+the appropriate storage backend for your configuration. Read
+the associated README file and execute the initialization script
+to create an empty PyKota Storage.
+
+Copy the conf/pykota.conf.sample sample configuration file to
+CUPS' configuration directory, usually /etc/cups, under the
+name pykota.conf.
+
+Adapt this file to your own needs and configuration.
+
+Modify the PPD files for each printer on which you want to manage
+print quotas, for example /etc/cups/ppd/lp.ppd :
+
+--- Add the line below exactly as-is somewhere near the top ---
+*cupsFilter:  "application/vnd.cups-postscript 0 pykota"
+--- Add the line above exactly as-is somewhere near the top  ---
+
+Do this in each ppd file present in this directory if you want
+to enable quota on every printer.
+          
+Add users to the quota system and set their quota values : 
+
+        $ edpykota username printer softlimit hardlimit
+
+Restart CUPS, for example under Debian GNU/Linux systems :          
+
+        $ /etc/init.d/cupsys restart
+        
+Your users now should be able to print but not exceed their 
+printing quota.
+
+============================================================
+
+Please e-mail bugs to: alet@librelogiciel.com (Jerome Alet)
Index: pykota/trunk/po/pykota.pot
===================================================================
--- pykota/trunk/po/pykota.pot (revision 695)
+++ pykota/trunk/po/pykota.pot (revision 695)
@@ -0,0 +1,22 @@
+# PyKota
+#
+# PyKota : Print Quotas for CUPS
+#
+# (c) 2003 Jerome Alet <alet@librelogiciel.com>
+# You're welcome to redistribute this software under the
+# terms of the GNU General Public Licence version 2.0
+# or, at your option, any higher version.
+#
+# You can read the complete GNU GPL in the file COPYING
+# which should come along with this software, or visit
+# the Free Software Foundation's WEB site http://www.fsf.org
+#
+# $Id$
+#
+# $Log$
+# Revision 1.1  2003/02/05 21:28:17  jalet
+# Initial import into CVS
+#
+#
+#
+
