root / pykota / trunk / pykota / storages / ldapstorage.py @ 3411

Revision 3411, 100.4 kB (checked in by jerome, 16 years ago)

Minor change to please emacs...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[3411]1# -*- coding: utf-8 -*-*-
[1016]2#
[3260]3# PyKota : Print Quotas for CUPS
[1016]4#
[3275]5# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
[3260]6# This program is free software: you can redistribute it and/or modify
[1016]7# it under the terms of the GNU General Public License as published by
[3260]8# the Free Software Foundation, either version 3 of the License, or
[1016]9# (at your option) any later version.
[3260]10#
[1016]11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
[3260]17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[1016]18#
19# $Id$
20#
[2107]21#
[1016]22
[3184]23"""This module defines a class to access to an LDAP database backend.
[1016]24
[3184]25My IANA assigned number, for
[3398]26"Conseil Internet & Logiciels Libres, Jerome Alet"
[3184]27is 16868. Use this as a base to extend the LDAP schema.
28"""
29
[2221]30import sys
[1240]31import types
[1030]32import time
33import md5
[2460]34import base64
[2679]35import random
36
[1522]37from mx import DateTime
[1016]38
[3288]39from pykota.errors import PyKotaStorageError
40from pykota.storage import BaseStorage, \
[2380]41                           StorageUser, StorageGroup, StoragePrinter, \
42                           StorageJob, StorageLastJob, StorageUserPQuota, \
43                           StorageGroupPQuota, StorageBillingCode
[3294]44                           
45from pykota.utils import *                           
[1016]46
47try :
48    import ldap
[1356]49    import ldap.modlist
[1016]50except ImportError :   
51    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0]
[2221]52else :   
53    try :
54        from ldap.cidict import cidict
55    except ImportError :   
56        import UserDict
57        sys.stderr.write("ERROR: PyKota requires a newer version of python-ldap. Workaround activated. Please upgrade python-ldap !\n")
58        class cidict(UserDict.UserDict) :
59            pass # Fake it all, and don't care for case insensitivity : users who need it will have to upgrade.
[1016]60   
[1130]61class Storage(BaseStorage) :
[1021]62    def __init__(self, pykotatool, host, dbname, user, passwd) :
[1016]63        """Opens the LDAP connection."""
[1966]64        self.savedtool = pykotatool
65        self.savedhost = host
66        self.saveddbname = dbname
67        self.saveduser = user
68        self.savedpasswd = passwd
69        self.secondStageInit()
70       
71    def secondStageInit(self) :   
72        """Second stage initialisation."""
73        BaseStorage.__init__(self, self.savedtool)
74        self.info = self.tool.config.getLDAPInfo()
75        message = ""
76        for tryit in range(3) :
77            try :
[2418]78                self.tool.logdebug("Trying to open database (host=%s, dbname=%s, user=%s)..." % (self.savedhost, self.saveddbname, self.saveduser))
[1966]79                self.database = ldap.initialize(self.savedhost) 
[1968]80                if self.info["ldaptls"] :
81                    # we want TLS
82                    ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.info["cacert"])
83                    self.database.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
84                    self.database.start_tls_s()
[1966]85                self.database.simple_bind_s(self.saveduser, self.savedpasswd)
86                self.basedn = self.saveddbname
87            except ldap.SERVER_DOWN :   
88                message = "LDAP backend for PyKota seems to be down !"
89                self.tool.printInfo("%s" % message, "error")
90                self.tool.printInfo("Trying again in 2 seconds...", "warn")
91                time.sleep(2)
92            except ldap.LDAPError :   
93                message = "Unable to connect to LDAP server %s as %s." % (self.savedhost, self.saveduser)
94                self.tool.printInfo("%s" % message, "error")
95                self.tool.printInfo("Trying again in 2 seconds...", "warn")
96                time.sleep(2)
97            else :   
98                self.useldapcache = self.tool.config.getLDAPCache()
99                if self.useldapcache :
100                    self.tool.logdebug("Low-Level LDAP Caching enabled.")
101                    self.ldapcache = {} # low-level cache specific to LDAP backend
102                self.closed = 0
103                self.tool.logdebug("Database opened (host=%s, dbname=%s, user=%s)" % (self.savedhost, self.saveddbname, self.saveduser))
104                return # All is fine here.
105        raise PyKotaStorageError, message         
[1016]106           
[1113]107    def close(self) :   
[1016]108        """Closes the database connection."""
109        if not self.closed :
[1170]110            self.database.unbind_s()
[1016]111            self.closed = 1
[1130]112            self.tool.logdebug("Database closed.")
[1016]113       
[1030]114    def genUUID(self) :   
115        """Generates an unique identifier.
116       
117           TODO : this one is not unique accross several print servers, but should be sufficient for testing.
118        """
[2680]119        return md5.md5("%s-%s" % (time.time(), random.random())).hexdigest()
[1030]120       
[1240]121    def normalizeFields(self, fields) :   
122        """Ensure all items are lists."""
123        for (k, v) in fields.items() :
124            if type(v) not in (types.TupleType, types.ListType) :
125                if not v :
126                    del fields[k]
127                else :   
128                    fields[k] = [ v ]
129        return fields       
130       
[1041]131    def beginTransaction(self) :   
132        """Starts a transaction."""
[1130]133        self.tool.logdebug("Transaction begins... WARNING : No transactions in LDAP !")
[1041]134       
135    def commitTransaction(self) :   
136        """Commits a transaction."""
[1130]137        self.tool.logdebug("Transaction committed. WARNING : No transactions in LDAP !")
[1041]138       
139    def rollbackTransaction(self) :     
140        """Rollbacks a transaction."""
[1130]141        self.tool.logdebug("Transaction aborted. WARNING : No transaction in LDAP !")
[1041]142       
[1356]143    def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE, flushcache=0) :
[1016]144        """Does an LDAP search query."""
[1966]145        message = ""
146        for tryit in range(3) :
147            try :
148                base = base or self.basedn
149                if self.useldapcache :
150                    # Here we overwrite the fields the app want, to try and
151                    # retrieve ALL user defined attributes ("*")
152                    # + the createTimestamp attribute, needed by job history
153                    #
154                    # This may not work with all LDAP servers
155                    # but works at least in OpenLDAP (2.1.25)
156                    # and iPlanet Directory Server (5.1 SP3)
157                    fields = ["*", "createTimestamp"]         
158                   
159                if self.useldapcache and (not flushcache) and (scope == ldap.SCOPE_BASE) and self.ldapcache.has_key(base) :
160                    entry = self.ldapcache[base]
161                    self.tool.logdebug("LDAP cache hit %s => %s" % (base, entry))
162                    result = [(base, entry)]
163                else :
[3294]164                    self.querydebug("QUERY : Filter : %s, BaseDN : %s, Scope : %s, Attributes : %s" % (key, base, scope, fields))
[1966]165                    result = self.database.search_s(base, scope, key, fields)
166            except ldap.NO_SUCH_OBJECT, msg :       
167                raise PyKotaStorageError, (_("Search base %s doesn't seem to exist. Probable misconfiguration. Please double check /etc/pykota/pykota.conf : %s") % (base, msg))
168            except ldap.LDAPError, msg :   
[3307]169                message = (_("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)) + " : %s" % msg
[1966]170                self.tool.printInfo("LDAP error : %s" % message, "error")
171                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
172                self.close()
173                self.secondStageInit()
174            else :     
[3294]175                self.querydebug("QUERY : Result : %s" % result)
[2221]176                result = [ (dn, cidict(attrs)) for (dn, attrs) in result ]
[1966]177                if self.useldapcache :
178                    for (dn, attributes) in result :
[3294]179                        self.querydebug("LDAP cache store %s => %s" % (dn, attributes))
[1966]180                        self.ldapcache[dn] = attributes
181                return result
182        raise PyKotaStorageError, message
[1030]183           
184    def doAdd(self, dn, fields) :
185        """Adds an entry in the LDAP directory."""
[2221]186        fields = self.normalizeFields(cidict(fields))
[1966]187        message = ""
188        for tryit in range(3) :
189            try :
[3294]190                self.querydebug("QUERY : ADD(%s, %s)" % (dn, fields))
[1966]191                entry = ldap.modlist.addModlist(fields)
[3294]192                self.querydebug("%s" % entry)
[1966]193                self.database.add_s(dn, entry)
[2749]194            except ldap.ALREADY_EXISTS, msg :       
[3307]195                raise PyKotaStorageError, "Entry %s already exists : %s" % (dn, msg)
[1966]196            except ldap.LDAPError, msg :
[3307]197                message = (_("Problem adding LDAP entry (%s, %s)") % (dn, str(fields))) + " : %s" % msg
[1966]198                self.tool.printInfo("LDAP error : %s" % message, "error")
199                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
200                self.close()
201                self.secondStageInit()
202            else :
203                if self.useldapcache :
[3294]204                    self.querydebug("LDAP cache add %s => %s" % (dn, fields))
[1966]205                    self.ldapcache[dn] = fields
206                return dn
207        raise PyKotaStorageError, message
[1030]208           
[1041]209    def doDelete(self, dn) :
210        """Deletes an entry from the LDAP directory."""
[1966]211        message = ""
212        for tryit in range(3) :
213            try :
[3294]214                self.querydebug("QUERY : Delete(%s)" % dn)
[1966]215                self.database.delete_s(dn)
[2751]216            except ldap.NO_SUCH_OBJECT :   
217                self.tool.printInfo("Entry %s was already missing before we deleted it. This **MAY** be normal." % dn, "info")
[1966]218            except ldap.LDAPError, msg :
[3307]219                message = (_("Problem deleting LDAP entry (%s)") % dn) + " : %s" % msg
[1966]220                self.tool.printInfo("LDAP error : %s" % message, "error")
221                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
222                self.close()
223                self.secondStageInit()
224            else :   
225                if self.useldapcache :
226                    try :
[3294]227                        self.querydebug("LDAP cache del %s" % dn)
[1966]228                        del self.ldapcache[dn]
229                    except KeyError :   
230                        pass
231                return       
232        raise PyKotaStorageError, message
[1041]233           
[1356]234    def doModify(self, dn, fields, ignoreold=1, flushcache=0) :
[1030]235        """Modifies an entry in the LDAP directory."""
[2221]236        fields = cidict(fields)
[1966]237        for tryit in range(3) :
238            try :
239                # TODO : take care of, and update LDAP specific cache
240                if self.useldapcache and not flushcache :
241                    if self.ldapcache.has_key(dn) :
242                        old = self.ldapcache[dn]
[3294]243                        self.querydebug("LDAP cache hit %s => %s" % (dn, old))
[1966]244                        oldentry = {}
245                        for (k, v) in old.items() :
246                            if k != "createTimestamp" :
247                                oldentry[k] = v
[1269]248                    else :   
[3294]249                        self.querydebug("LDAP cache miss %s" % dn)
[1966]250                        oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)[0][1]
251                else :       
252                    oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE, flushcache=flushcache)[0][1]
253                for (k, v) in fields.items() :
254                    if type(v) == type({}) :
255                        try :
256                            oldvalue = v["convert"](oldentry.get(k, [0])[0])
257                        except ValueError :   
[3294]258                            self.querydebug("Error converting %s with %s(%s)" % (oldentry.get(k), k, v))
[1966]259                            oldvalue = 0
260                        if v["operator"] == '+' :
261                            newvalue = oldvalue + v["value"]
262                        else :   
263                            newvalue = oldvalue - v["value"]
264                        fields[k] = str(newvalue)
265                fields = self.normalizeFields(fields)
[3294]266                self.querydebug("QUERY : Modify(%s, %s ==> %s)" % (dn, oldentry, fields))
[1966]267                entry = ldap.modlist.modifyModlist(oldentry, fields, ignore_oldexistent=ignoreold)
268                modentry = []
[1356]269                for (mop, mtyp, mval) in entry :
[2191]270                    if mtyp and (mtyp.lower() != "createtimestamp") :
[1966]271                        modentry.append((mop, mtyp, mval))
[3294]272                self.querydebug("MODIFY : %s ==> %s ==> %s" % (fields, entry, modentry))
[1966]273                if modentry :
274                    self.database.modify_s(dn, modentry)
275            except ldap.LDAPError, msg :
[3307]276                message = (_("Problem modifying LDAP entry (%s, %s)") % (dn, fields)) + " : %s" % msg
[1966]277                self.tool.printInfo("LDAP error : %s" % message, "error")
278                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
279                self.close()
280                self.secondStageInit()
281            else :
282                if self.useldapcache :
283                    cachedentry = self.ldapcache[dn]
284                    for (mop, mtyp, mval) in entry :
285                        if mop in (ldap.MOD_ADD, ldap.MOD_REPLACE) :
286                            cachedentry[mtyp] = mval
287                        else :
288                            try :
289                                del cachedentry[mtyp]
290                            except KeyError :   
291                                pass
[3294]292                    self.querydebug("LDAP cache update %s => %s" % (dn, cachedentry))
[1966]293                return dn
294        raise PyKotaStorageError, message
[1016]295           
[2652]296    def filterNames(self, records, attribute, patterns=None) :
[2107]297        """Returns a list of 'attribute' from a list of records.
298       
299           Logs any missing attribute.
300        """   
301        result = []
[2191]302        for (dn, record) in records :
303            attrval = record.get(attribute, [None])[0]
[2107]304            if attrval is None :
[2191]305                self.tool.printInfo("Object %s has no %s attribute !" % (dn, attribute), "error")
[2652]306            else :
[3294]307                attrval = databaseToUnicode(attrval)
[2652]308                if patterns :
309                    if (not isinstance(patterns, type([]))) and (not isinstance(patterns, type(()))) :
310                        patterns = [ patterns ]
311                    if self.tool.matchString(attrval, patterns) :   
312                        result.append(attrval)
313                else :   
314                    result.append(attrval)
[2107]315        return result       
316               
[2386]317    def getAllBillingCodes(self, billingcode=None) :   
318        """Extracts all billing codes or only the billing codes matching the optional parameter."""
319        ldapfilter = "objectClass=pykotaBilling"
320        result = self.doSearch(ldapfilter, ["pykotaBillingCode"], base=self.info["billingcodebase"])
321        if result :
[3364]322            return self.filterNames(result, "pykotaBillingCode", billingcode)
[2652]323        else :   
324            return []
[2386]325       
[1993]326    def getAllPrintersNames(self, printername=None) :   
327        """Extracts all printer names or only the printers' names matching the optional parameter."""
328        ldapfilter = "objectClass=pykotaPrinter"
329        result = self.doSearch(ldapfilter, ["pykotaPrinterName"], base=self.info["printerbase"])
[1754]330        if result :
[2652]331            return self.filterNames(result, "pykotaPrinterName", printername)
332        else :   
333            return []
[1754]334       
[1993]335    def getAllUsersNames(self, username=None) :   
336        """Extracts all user names or only the users' names matching the optional parameter."""
337        ldapfilter = "objectClass=pykotaAccount"
338        result = self.doSearch(ldapfilter, ["pykotaUserName"], base=self.info["userbase"])
[1179]339        if result :
[2652]340            return self.filterNames(result, "pykotaUserName", username)
341        else :   
342            return []
[1179]343       
[1993]344    def getAllGroupsNames(self, groupname=None) :   
345        """Extracts all group names or only the groups' names matching the optional parameter."""
346        ldapfilter = "objectClass=pykotaGroup"
347        result = self.doSearch(ldapfilter, ["pykotaGroupName"], base=self.info["groupbase"])
[1179]348        if result :
[2652]349            return self.filterNames(result, "pykotaGroupName", groupname)
350        else :   
351            return []
[1179]352       
[1806]353    def getUserNbJobsFromHistory(self, user) :
354        """Returns the number of jobs the user has in history."""
[3294]355        result = self.doSearch("(&(pykotaUserName=%s)(objectClass=pykotaJob))" % unicodeToDatabase(user.Name), None, base=self.info["jobbase"])
[1806]356        return len(result)
357       
[1130]358    def getUserFromBackend(self, username) :   
[1041]359        """Extracts user information given its name."""
360        user = StorageUser(self, username)
[3294]361        username = unicodeToDatabase(username)
[3086]362        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "description"], base=self.info["userbase"])
[1016]363        if result :
[1041]364            fields = result[0][1]
365            user.ident = result[0][0]
[3294]366            user.Description = databaseToUnicode(fields.get("description", [None])[0])
[3364]367            user.Email = databaseToUnicode(fields.get(self.info["usermail"], [None])[0])
[3363]368            user.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[3086]369            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["balancerdn"], username), ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments", "pykotaOverCharge"], base=self.info["balancebase"])
[1742]370            if not result :
371                raise PyKotaStorageError, _("No pykotaAccountBalance object found for user %s. Did you create LDAP entries manually ?") % username
372            else :
[1041]373                fields = result[0][1]
374                user.idbalance = result[0][0]
375                user.AccountBalance = fields.get("pykotaBalance")
376                if user.AccountBalance is not None :
377                    if user.AccountBalance[0].upper() == "NONE" :
378                        user.AccountBalance = None
379                    else :   
380                        user.AccountBalance = float(user.AccountBalance[0])
381                user.AccountBalance = user.AccountBalance or 0.0       
382                user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
[3086]383                user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
[1041]384                if user.LifeTimePaid is not None :
385                    if user.LifeTimePaid[0].upper() == "NONE" :
386                        user.LifeTimePaid = None
387                    else :   
388                        user.LifeTimePaid = float(user.LifeTimePaid[0])
389                user.LifeTimePaid = user.LifeTimePaid or 0.0       
[1522]390                user.Payments = []
391                for payment in fields.get("pykotaPayments", []) :
[2452]392                    try :
393                        (date, amount, description) = payment.split(" # ")
394                    except ValueError :
395                        # Payment with no description (old Payment)
396                        (date, amount) = payment.split(" # ")
397                        description = ""
398                    else :   
[3294]399                        description = databaseToUnicode(base64.decodestring(description))
[3244]400                    if amount.endswith(" #") :   
401                        amount = amount[:-2] # TODO : should be catched earlier, the bug is above I think
[2452]402                    user.Payments.append((date, float(amount), description))
[2950]403            user.Exists = True
[1041]404        return user
405       
[1130]406    def getGroupFromBackend(self, groupname) :   
[1041]407        """Extracts group information given its name."""
408        group = StorageGroup(self, groupname)
[3294]409        groupname = unicodeToDatabase(groupname)
[2721]410        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), ["pykotaGroupName", "pykotaLimitBy", "description"], base=self.info["groupbase"])
[1030]411        if result :
[1041]412            fields = result[0][1]
413            group.ident = result[0][0]
[3364]414            group.Name = databaseToUnicode(fields.get("pykotaGroupName", [groupname])[0])
[3294]415            group.Description = databaseToUnicode(fields.get("description", [None])[0])
[3363]416            group.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[1041]417            group.AccountBalance = 0.0
418            group.LifeTimePaid = 0.0
[1137]419            for member in self.getGroupMembers(group) :
[1075]420                if member.Exists :
421                    group.AccountBalance += member.AccountBalance
422                    group.LifeTimePaid += member.LifeTimePaid
[2950]423            group.Exists = True
[1041]424        return group
425       
[1130]426    def getPrinterFromBackend(self, printername) :       
[1451]427        """Extracts printer information given its name : returns first matching printer."""
[1041]428        printer = StoragePrinter(self, printername)
[3294]429        printername = unicodeToDatabase(printername)
[2459]430        result = self.doSearch("(&(objectClass=pykotaPrinter)(|(pykotaPrinterName=%s)(%s=%s)))" \
431                      % (printername, self.info["printerrdn"], printername), \
432                        ["pykotaPrinterName", "pykotaPricePerPage", \
433                         "pykotaPricePerJob", "pykotaMaxJobSize", \
434                         "pykotaPassThrough", "uniqueMember", "description"], \
435                      base=self.info["printerbase"])
[1016]436        if result :
[1451]437            fields = result[0][1]       # take only first matching printer, ignore the rest
[1041]438            printer.ident = result[0][0]
[3364]439            printer.Name = databaseToUnicode(fields.get("pykotaPrinterName", [printername])[0])
[2054]440            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0])
441            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0])
[2459]442            printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
443            printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
444            if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
445                printer.PassThrough = 1
446            else :
447                printer.PassThrough = 0
[1258]448            printer.uniqueMember = fields.get("uniqueMember", [])
[3294]449            printer.Description = databaseToUnicode(fields.get("description", [""])[0]) 
[2950]450            printer.Exists = True
[1041]451        return printer   
452       
[1130]453    def getUserPQuotaFromBackend(self, user, printer) :       
[1041]454        """Extracts a user print quota."""
455        userpquota = StorageUserPQuota(self, user, printer)
[1228]456        if printer.Exists and user.Exists :
[1969]457            if self.info["userquotabase"].lower() == "user" :
[1998]458                base = user.ident
[1969]459            else :   
[1998]460                base = self.info["userquotabase"]
[2652]461            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % \
[3294]462                                      (unicodeToDatabase(user.Name), unicodeToDatabase(printer.Name)), \
[2749]463                                      ["pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount", "pykotaMaxJobSize"], \
[2652]464                                      base=base)
[1017]465            if result :
[1041]466                fields = result[0][1]
467                userpquota.ident = result[0][0]
[2054]468                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
469                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
470                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
[1041]471                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
472                if userpquota.SoftLimit is not None :
473                    if userpquota.SoftLimit[0].upper() == "NONE" :
474                        userpquota.SoftLimit = None
475                    else :   
476                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
477                userpquota.HardLimit = fields.get("pykotaHardLimit")
478                if userpquota.HardLimit is not None :
479                    if userpquota.HardLimit[0].upper() == "NONE" :
480                        userpquota.HardLimit = None
481                    elif userpquota.HardLimit is not None :   
482                        userpquota.HardLimit = int(userpquota.HardLimit[0])
483                userpquota.DateLimit = fields.get("pykotaDateLimit")
484                if userpquota.DateLimit is not None :
485                    if userpquota.DateLimit[0].upper() == "NONE" : 
486                        userpquota.DateLimit = None
487                    else :   
488                        userpquota.DateLimit = userpquota.DateLimit[0]
[2749]489                userpquota.MaxJobSize = fields.get("pykotaMaxJobSize")
490                if userpquota.MaxJobSize is not None :
491                    if userpquota.MaxJobSize[0].upper() == "NONE" :
492                        userpquota.MaxJobSize = None
493                    else :   
494                        userpquota.MaxJobSize = int(userpquota.MaxJobSize[0])
[2950]495                userpquota.Exists = True
[1041]496        return userpquota
[1016]497       
[1130]498    def getGroupPQuotaFromBackend(self, group, printer) :       
[1041]499        """Extracts a group print quota."""
500        grouppquota = StorageGroupPQuota(self, group, printer)
501        if group.Exists :
[1969]502            if self.info["groupquotabase"].lower() == "group" :
[1998]503                base = group.ident
[1969]504            else :   
[1998]505                base = self.info["groupquotabase"]
[2652]506            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % \
[3294]507                                      (unicodeToDatabase(group.Name), unicodeToDatabase(printer.Name)), \
[2749]508                                      ["pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaMaxJobSize"], \
[2652]509                                      base=base)
[1041]510            if result :
511                fields = result[0][1]
512                grouppquota.ident = result[0][0]
513                grouppquota.SoftLimit = fields.get("pykotaSoftLimit")
514                if grouppquota.SoftLimit is not None :
515                    if grouppquota.SoftLimit[0].upper() == "NONE" :
516                        grouppquota.SoftLimit = None
517                    else :   
518                        grouppquota.SoftLimit = int(grouppquota.SoftLimit[0])
519                grouppquota.HardLimit = fields.get("pykotaHardLimit")
520                if grouppquota.HardLimit is not None :
521                    if grouppquota.HardLimit[0].upper() == "NONE" :
522                        grouppquota.HardLimit = None
523                    else :   
524                        grouppquota.HardLimit = int(grouppquota.HardLimit[0])
525                grouppquota.DateLimit = fields.get("pykotaDateLimit")
526                if grouppquota.DateLimit is not None :
527                    if grouppquota.DateLimit[0].upper() == "NONE" : 
528                        grouppquota.DateLimit = None
529                    else :   
530                        grouppquota.DateLimit = grouppquota.DateLimit[0]
[2749]531                grouppquota.MaxJobSize = fields.get("pykotaMaxJobSize")
532                if grouppquota.MaxJobSize is not None :
533                    if grouppquota.MaxJobSize[0].upper() == "NONE" :
534                        grouppquota.MaxJobSize = None
535                    else :   
536                        grouppquota.MaxJobSize = int(grouppquota.MaxJobSize[0])
[1041]537                grouppquota.PageCounter = 0
538                grouppquota.LifePageCounter = 0
[3294]539                usernamesfilter = "".join(["(pykotaUserName=%s)" % unicodeToDatabase(member.Name) for member in self.getGroupMembers(group)])
[1361]540                if usernamesfilter :
541                    usernamesfilter = "(|%s)" % usernamesfilter
[1998]542                if self.info["userquotabase"].lower() == "user" :
543                    base = self.info["userbase"]
544                else :
545                    base = self.info["userquotabase"]
[2652]546                result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)%s)" % \
[3294]547                                          (unicodeToDatabase(printer.Name), usernamesfilter), \
[2652]548                                          ["pykotaPageCounter", "pykotaLifePageCounter"], base=base)
[1041]549                if result :
550                    for userpquota in result :   
[1392]551                        grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0)
552                        grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0)
[2950]553                grouppquota.Exists = True
[1041]554        return grouppquota
555       
[1130]556    def getPrinterLastJobFromBackend(self, printer) :       
[1041]557        """Extracts a printer's last job information."""
558        lastjob = StorageLastJob(self, printer)
[3294]559        pname = unicodeToDatabase(printer.Name)
[2652]560        result = self.doSearch("(&(objectClass=pykotaLastjob)(|(pykotaPrinterName=%s)(%s=%s)))" % \
561                                  (pname, self.info["printerrdn"], pname), \
562                                  ["pykotaLastJobIdent"], \
563                                  base=self.info["lastjobbase"])
[1016]564        if result :
[1041]565            lastjob.lastjobident = result[0][0]
566            lastjobident = result[0][1]["pykotaLastJobIdent"][0]
[1692]567            result = None
568            try :
[2211]569                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes", 
570                                                                  "pykotaHostName", 
571                                                                  "pykotaUserName", 
572                                                                  "pykotaPrinterName", 
573                                                                  "pykotaJobId", 
574                                                                  "pykotaPrinterPageCounter", 
575                                                                  "pykotaJobSize", 
576                                                                  "pykotaAction", 
577                                                                  "pykotaJobPrice", 
578                                                                  "pykotaFileName", 
579                                                                  "pykotaTitle", 
580                                                                  "pykotaCopies", 
581                                                                  "pykotaOptions", 
582                                                                  "pykotaBillingCode", 
583                                                                  "pykotaPages", 
584                                                                  "pykotaMD5Sum", 
[2455]585                                                                  "pykotaPrecomputedJobSize",
586                                                                  "pykotaPrecomputedJobPrice",
[2211]587                                                                  "createTimestamp" ], 
588                                                                base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
[1692]589            except PyKotaStorageError :   
590                pass # Last job entry exists, but job probably doesn't exist anymore.
[1017]591            if result :
[1041]592                fields = result[0][1]
593                lastjob.ident = result[0][0]
[3364]594                lastjob.JobId = databaseToUnicode(fields.get("pykotaJobId")[0])
[3294]595                lastjob.UserName = databaseToUnicode(fields.get("pykotaUserName")[0])
[2054]596                lastjob.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0])
[1601]597                try :
598                    lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0])
599                except ValueError :   
600                    lastjob.JobSize = None
601                try :   
602                    lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
603                except ValueError :   
604                    lastjob.JobPrice = None
[3364]605                lastjob.JobAction = databaseToUnicode(fields.get("pykotaAction", [""])[0])
[3294]606                lastjob.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0]) 
607                lastjob.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0]) 
[1203]608                lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0])
[3294]609                lastjob.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0]) 
[3364]610                lastjob.JobHostName = databaseToUnicode(fields.get("pykotaHostName", [""])[0])
[1520]611                lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
[3294]612                lastjob.JobBillingCode = databaseToUnicode(fields.get("pykotaBillingCode", [None])[0])
[3364]613                lastjob.JobMD5Sum = databaseToUnicode(fields.get("pykotaMD5Sum", [None])[0])
[2054]614                lastjob.JobPages = fields.get("pykotaPages", [""])[0]
[2455]615                try :
616                    lastjob.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
617                except ValueError :   
618                    lastjob.PrecomputedJobSize = None
619                try :   
620                    lastjob.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
621                except ValueError :   
622                    lastjob.PrecomputedJobPrice = None
[3364]623                if lastjob.JobTitle == lastjob.JobFileName == lastjob.JobOptions == u"hidden" :
[2287]624                    (lastjob.JobTitle, lastjob.JobFileName, lastjob.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
[2538]625                date = fields.get("createTimestamp", ["19700101000000Z"])[0] # It's in UTC !
626                mxtime = DateTime.strptime(date[:14], "%Y%m%d%H%M%S").localtime()
[2953]627                lastjob.JobDate = mxtime.strftime("%Y-%m-%d %H:%M:%S")
[2950]628                lastjob.Exists = True
[1041]629        return lastjob
[1016]630       
[1137]631    def getGroupMembersFromBackend(self, group) :       
632        """Returns the group's members list."""
633        groupmembers = []
[3294]634        gname = unicodeToDatabase(group.Name)
[2652]635        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % \
636                                  (gname, self.info["grouprdn"], gname), \
637                                  [self.info["groupmembers"]], \
638                                  base=self.info["groupbase"])
[1137]639        if result :
640            for username in result[0][1].get(self.info["groupmembers"], []) :
[3294]641                groupmembers.append(self.getUser(databaseToUnicode(username)))
[1137]642        return groupmembers       
643       
644    def getUserGroupsFromBackend(self, user) :       
[1130]645        """Returns the user's groups list."""
646        groups = []
[3294]647        uname = unicodeToDatabase(user.Name)
[2652]648        result = self.doSearch("(&(objectClass=pykotaGroup)(%s=%s))" % \
649                                  (self.info["groupmembers"], uname), \
650                                  [self.info["grouprdn"], "pykotaGroupName", "pykotaLimitBy"], \
651                                  base=self.info["groupbase"])
[1130]652        if result :
653            for (groupid, fields) in result :
[3294]654                groupname = databaseToUnicode((fields.get("pykotaGroupName", [None]) or fields.get(self.info["grouprdn"], [None]))[0])
[1147]655                group = self.getFromCache("GROUPS", groupname)
656                if group is None :
657                    group = StorageGroup(self, groupname)
658                    group.ident = groupid
659                    group.LimitBy = fields.get("pykotaLimitBy")
660                    if group.LimitBy is not None :
[3363]661                        group.LimitBy = databaseToUnicode(group.LimitBy[0])
[2000]662                    else :   
[3363]663                        group.LimitBy = u"quota"
[1147]664                    group.AccountBalance = 0.0
665                    group.LifeTimePaid = 0.0
666                    for member in self.getGroupMembers(group) :
667                        if member.Exists :
668                            group.AccountBalance += member.AccountBalance
669                            group.LifeTimePaid += member.LifeTimePaid
[2950]670                    group.Exists = True
[1147]671                    self.cacheEntry("GROUPS", group.Name, group)
672                groups.append(group)
[1130]673        return groups       
674       
[1249]675    def getParentPrintersFromBackend(self, printer) :   
676        """Get all the printer groups this printer is a member of."""
677        pgroups = []
[2652]678        result = self.doSearch("(&(objectClass=pykotaPrinter)(uniqueMember=%s))" % \
679                                  printer.ident, \
680                                  ["pykotaPrinterName"], \
681                                  base=self.info["printerbase"])
[1249]682        if result :
683            for (printerid, fields) in result :
684                if printerid != printer.ident : # In case of integrity violation.
[3294]685                    parentprinter = self.getPrinter(databaseToUnicode(fields.get("pykotaPrinterName")[0]))
[1249]686                    if parentprinter.Exists :
687                        pgroups.append(parentprinter)
688        return pgroups
689       
[1041]690    def getMatchingPrinters(self, printerpattern) :
691        """Returns the list of all printers for which name matches a certain pattern."""
692        printers = []
693        # see comment at the same place in pgstorage.py
[2657]694        result = self.doSearch("objectClass=pykotaPrinter", \
[2652]695                                  ["pykotaPrinterName", "pykotaPricePerPage", "pykotaPricePerJob", "pykotaMaxJobSize", "pykotaPassThrough", "uniqueMember", "description"], \
696                                  base=self.info["printerbase"])
[1016]697        if result :
[2657]698            patterns = printerpattern.split(",")
[2754]699            try :
700                patdict = {}.fromkeys(patterns)
701            except AttributeError :   
702                # Python v2.2 or earlier
703                patdict = {}
704                for p in patterns :
705                    patdict[p] = None
[1041]706            for (printerid, fields) in result :
[3294]707                printername = databaseToUnicode(fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0])
[2754]708                if patdict.has_key(printername) or self.tool.matchString(printername, patterns) :
[2657]709                    printer = StoragePrinter(self, printername)
710                    printer.ident = printerid
711                    printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
712                    printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
713                    printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
714                    printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
715                    if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
716                        printer.PassThrough = 1
717                    else :
718                        printer.PassThrough = 0
719                    printer.uniqueMember = fields.get("uniqueMember", [])
[3294]720                    printer.Description = databaseToUnicode(fields.get("description", [""])[0]) 
[2950]721                    printer.Exists = True
[2657]722                    printers.append(printer)
723                    self.cacheEntry("PRINTERS", printer.Name, printer)
[1041]724        return printers       
[1016]725       
[2657]726    def getMatchingUsers(self, userpattern) :
727        """Returns the list of all users for which name matches a certain pattern."""
728        users = []
729        # see comment at the same place in pgstorage.py
730        result = self.doSearch("objectClass=pykotaAccount", \
[3086]731                                  ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "description"], \
[2657]732                                  base=self.info["userbase"])
733        if result :
734            patterns = userpattern.split(",")
[2754]735            try :
736                patdict = {}.fromkeys(patterns)
737            except AttributeError :   
738                # Python v2.2 or earlier
739                patdict = {}
740                for p in patterns :
741                    patdict[p] = None
[2657]742            for (userid, fields) in result :
[3294]743                username = databaseToUnicode(fields.get("pykotaUserName", [""])[0] or fields.get(self.info["userrdn"], [""])[0])
[2754]744                if patdict.has_key(username) or self.tool.matchString(username, patterns) :
[2657]745                    user = StorageUser(self, username)
746                    user.ident = userid
[3364]747                    user.Email = databaseToUnicode(fields.get(self.info["usermail"], [None])[0])
[3363]748                    user.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[3294]749                    user.Description = databaseToUnicode(fields.get("description", [""])[0]) 
750                    uname = unicodeToDatabase(username)
[2657]751                    result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \
752                                              (uname, self.info["balancerdn"], uname), \
[3086]753                                              ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments", "pykotaOverCharge"], \
[2657]754                                              base=self.info["balancebase"])
755                    if not result :
756                        raise PyKotaStorageError, _("No pykotaAccountBalance object found for user %s. Did you create LDAP entries manually ?") % username
757                    else :
758                        fields = result[0][1]
759                        user.idbalance = result[0][0]
[3086]760                        user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
[2657]761                        user.AccountBalance = fields.get("pykotaBalance")
762                        if user.AccountBalance is not None :
763                            if user.AccountBalance[0].upper() == "NONE" :
764                                user.AccountBalance = None
765                            else :   
766                                user.AccountBalance = float(user.AccountBalance[0])
767                        user.AccountBalance = user.AccountBalance or 0.0       
768                        user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
769                        if user.LifeTimePaid is not None :
770                            if user.LifeTimePaid[0].upper() == "NONE" :
771                                user.LifeTimePaid = None
772                            else :   
773                                user.LifeTimePaid = float(user.LifeTimePaid[0])
774                        user.LifeTimePaid = user.LifeTimePaid or 0.0       
775                        user.Payments = []
776                        for payment in fields.get("pykotaPayments", []) :
777                            try :
778                                (date, amount, description) = payment.split(" # ")
779                            except ValueError :
780                                # Payment with no description (old Payment)
781                                (date, amount) = payment.split(" # ")
782                                description = ""
783                            else :   
[3294]784                                description = databaseToUnicode(base64.decodestring(description))
[3243]785                            if amount.endswith(" #") :   
786                                amount = amount[:-2] # TODO : should be catched earlier, the bug is above I think
[2657]787                            user.Payments.append((date, float(amount), description))
[2950]788                    user.Exists = True
[2657]789                    users.append(user)
790                    self.cacheEntry("USERS", user.Name, user)
791        return users       
792       
793    def getMatchingGroups(self, grouppattern) :
794        """Returns the list of all groups for which name matches a certain pattern."""
795        groups = []
796        # see comment at the same place in pgstorage.py
797        result = self.doSearch("objectClass=pykotaGroup", \
[2721]798                                  ["pykotaGroupName", "pykotaLimitBy", "description"], \
[2657]799                                  base=self.info["groupbase"])
800        if result :
801            patterns = grouppattern.split(",")
[2754]802            try :
803                patdict = {}.fromkeys(patterns)
804            except AttributeError :   
805                # Python v2.2 or earlier
806                patdict = {}
807                for p in patterns :
808                    patdict[p] = None
[2657]809            for (groupid, fields) in result :
[3294]810                groupname = databaseToUnicode(fields.get("pykotaGroupName", [""])[0] or fields.get(self.info["grouprdn"], [""])[0])
[2754]811                if patdict.has_key(groupname) or self.tool.matchString(groupname, patterns) :
[2657]812                    group = StorageGroup(self, groupname)
813                    group.ident = groupid
[3364]814                    group.Name = databaseToUnicode(fields.get("pykotaGroupName", [groupname])[0]) 
[3363]815                    group.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[3294]816                    group.Description = databaseToUnicode(fields.get("description", [""])[0]) 
[2657]817                    group.AccountBalance = 0.0
818                    group.LifeTimePaid = 0.0
819                    for member in self.getGroupMembers(group) :
820                        if member.Exists :
821                            group.AccountBalance += member.AccountBalance
822                            group.LifeTimePaid += member.LifeTimePaid
[2950]823                    group.Exists = True
[2752]824                    groups.append(group)
825                    self.cacheEntry("GROUPS", group.Name, group)
[2657]826        return groups
827       
[1133]828    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :       
[1041]829        """Returns the list of users who uses a given printer, along with their quotas."""
830        usersandquotas = []
[3294]831        pname = unicodeToDatabase(printer.Name)
832        names = [unicodeToDatabase(n) for n in names]
[1998]833        if self.info["userquotabase"].lower() == "user" :
[2830]834            base = self.info["userbase"]
[1998]835        else :
[2830]836            base = self.info["userquotabase"]
[2652]837        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)(|%s))" % \
838                                  (pname, "".join(["(pykotaUserName=%s)" % uname for uname in names])), \
839                                  ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], \
840                                  base=base)
[1041]841        if result :
842            for (userquotaid, fields) in result :
[3294]843                user = self.getUser(databaseToUnicode(fields.get("pykotaUserName")[0]))
[1133]844                userpquota = StorageUserPQuota(self, user, printer)
845                userpquota.ident = userquotaid
[2054]846                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
847                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
848                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
[1133]849                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
850                if userpquota.SoftLimit is not None :
851                    if userpquota.SoftLimit[0].upper() == "NONE" :
852                        userpquota.SoftLimit = None
853                    else :   
854                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
855                userpquota.HardLimit = fields.get("pykotaHardLimit")
856                if userpquota.HardLimit is not None :
857                    if userpquota.HardLimit[0].upper() == "NONE" :
858                        userpquota.HardLimit = None
859                    elif userpquota.HardLimit is not None :   
860                        userpquota.HardLimit = int(userpquota.HardLimit[0])
861                userpquota.DateLimit = fields.get("pykotaDateLimit")
862                if userpquota.DateLimit is not None :
863                    if userpquota.DateLimit[0].upper() == "NONE" : 
864                        userpquota.DateLimit = None
865                    else :   
866                        userpquota.DateLimit = userpquota.DateLimit[0]
[2950]867                userpquota.Exists = True
[1133]868                usersandquotas.append((user, userpquota))
869                self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
[1051]870        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
[1041]871        return usersandquotas
872               
[1133]873    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :       
[1041]874        """Returns the list of groups which uses a given printer, along with their quotas."""
875        groupsandquotas = []
[3294]876        pname = unicodeToDatabase(printer.Name)
877        names = [unicodeToDatabase(n) for n in names]
[1998]878        if self.info["groupquotabase"].lower() == "group" :
[2830]879            base = self.info["groupbase"]
[1998]880        else :
[2830]881            base = self.info["groupquotabase"]
[2652]882        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s)(|%s))" % \
883                                  (pname, "".join(["(pykotaGroupName=%s)" % gname for gname in names])), \
884                                  ["pykotaGroupName"], \
885                                  base=base)
[1041]886        if result :
887            for (groupquotaid, fields) in result :
[3294]888                group = self.getGroup(databaseToUnicode(fields.get("pykotaGroupName")[0]))
[1133]889                grouppquota = self.getGroupPQuota(group, printer)
890                groupsandquotas.append((group, grouppquota))
[1051]891        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
[1041]892        return groupsandquotas
[1016]893       
[2768]894    def addPrinter(self, printer) :
895        """Adds a printer to the quota storage, returns the old value if it already exists."""
896        oldentry = self.getPrinter(printer.Name)
897        if oldentry.Exists :
898            return oldentry # we return the existing entry
[3294]899        printername = unicodeToDatabase(printer.Name)
[1030]900        fields = { self.info["printerrdn"] : printername,
901                   "objectClass" : ["pykotaObject", "pykotaPrinter"],
[1041]902                   "cn" : printername,
[1030]903                   "pykotaPrinterName" : printername,
[2768]904                   "pykotaPassThrough" : (printer.PassThrough and "t") or "f",
905                   "pykotaMaxJobSize" : str(printer.MaxJobSize or 0),
[3294]906                   "description" : unicodeToDatabase(printer.Description or ""),
[2768]907                   "pykotaPricePerPage" : str(printer.PricePerPage or 0.0),
908                   "pykotaPricePerJob" : str(printer.PricePerJob or 0.0),
[1030]909                 } 
910        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
[1041]911        self.doAdd(dn, fields)
[2768]912        printer.isDirty = False
913        return None # the entry created doesn't need further modification
[1016]914       
[1041]915    def addUser(self, user) :       
[2773]916        """Adds a user to the quota storage, returns the old value if it already exists."""
917        oldentry = self.getUser(user.Name)
918        if oldentry.Exists :
919            return oldentry # we return the existing entry
[3294]920        uname = unicodeToDatabase(user.Name)
[1105]921        newfields = {
[2652]922                       "pykotaUserName" : uname,
[3363]923                       "pykotaLimitBy" : unicodeToDatabase(user.LimitBy or u"quota"),
[3294]924                       "description" : unicodeToDatabase(user.Description or ""),
[3364]925                       self.info["usermail"] : unicodeToDatabase(user.Email or ""),
[1105]926                    }   
[1742]927                       
[1105]928        mustadd = 1
929        if self.info["newuser"].lower() != 'below' :
[1510]930            try :
931                (where, action) = [s.strip() for s in self.info["newuser"].split(",")]
932            except ValueError :
933                (where, action) = (self.info["newuser"].strip(), "fail")
[2652]934            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % \
935                                      (where, self.info["userrdn"], uname), \
936                                      None, \
937                                      base=self.info["userbase"])
[1105]938            if result :
939                (dn, fields) = result[0]
[2188]940                oc = fields.get("objectClass", fields.get("objectclass", []))
941                oc.extend(["pykotaAccount", "pykotaAccountBalance"])
[1105]942                fields.update(newfields)
[1742]943                fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0),
[3086]944                                "pykotaOverCharge" : str(user.OverCharge),
[1742]945                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })   
[1105]946                self.doModify(dn, fields)
947                mustadd = 0
[1510]948            else :
[1534]949                message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name)
[1510]950                if action.lower() == "warn" :   
[2191]951                    self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn")
[1510]952                else : # 'fail' or incorrect setting
953                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
[1105]954               
955        if mustadd :
[1742]956            if self.info["userbase"] == self.info["balancebase"] :           
[2652]957                fields = { self.info["userrdn"] : uname,
[1742]958                           "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
[2652]959                           "cn" : uname,
[1742]960                           "pykotaBalance" : str(user.AccountBalance or 0.0),
[3086]961                           "pykotaOverCharge" : str(user.OverCharge),
[1742]962                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
963                         } 
964            else :             
[2652]965                fields = { self.info["userrdn"] : uname,
[1742]966                           "objectClass" : ["pykotaObject", "pykotaAccount"],
[2652]967                           "cn" : uname,
[1742]968                         } 
[1105]969            fields.update(newfields)         
[2652]970            dn = "%s=%s,%s" % (self.info["userrdn"], uname, self.info["userbase"])
[1105]971            self.doAdd(dn, fields)
[1742]972            if self.info["userbase"] != self.info["balancebase"] :           
[2652]973                fields = { self.info["balancerdn"] : uname,
[1742]974                           "objectClass" : ["pykotaObject", "pykotaAccountBalance"],
[2652]975                           "cn" : uname,
[1742]976                           "pykotaBalance" : str(user.AccountBalance or 0.0),
[3086]977                           "pykotaOverCharge" : str(user.OverCharge),
[1742]978                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
979                         } 
[2652]980                dn = "%s=%s,%s" % (self.info["balancerdn"], uname, self.info["balancebase"])
[1742]981                self.doAdd(dn, fields)
[2773]982        user.idbalance = dn
983        if user.PaymentsBacklog :
984            for (value, comment) in user.PaymentsBacklog :
985                self.writeNewPayment(user, value, comment)
986            user.PaymentsBacklog = []
987        user.isDirty = False
988        return None # the entry created doesn't need further modification
[1016]989       
[1041]990    def addGroup(self, group) :       
[2773]991        """Adds a group to the quota storage, returns the old value if it already exists."""
992        oldentry = self.getGroup(group.Name)
993        if oldentry.Exists :
994            return oldentry # we return the existing entry
[3294]995        gname = unicodeToDatabase(group.Name)
[1105]996        newfields = { 
[2652]997                      "pykotaGroupName" : gname,
[3363]998                      "pykotaLimitBy" : unicodeToDatabase(group.LimitBy or u"quota"),
[3294]999                      "description" : unicodeToDatabase(group.Description or "")
[1105]1000                    } 
1001        mustadd = 1
1002        if self.info["newgroup"].lower() != 'below' :
[1510]1003            try :
1004                (where, action) = [s.strip() for s in self.info["newgroup"].split(",")]
1005            except ValueError :
1006                (where, action) = (self.info["newgroup"].strip(), "fail")
[2652]1007            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % \
1008                                      (where, self.info["grouprdn"], gname), \
1009                                      None, \
1010                                      base=self.info["groupbase"])
[1105]1011            if result :
1012                (dn, fields) = result[0]
[2188]1013                oc = fields.get("objectClass", fields.get("objectclass", []))
1014                oc.extend(["pykotaGroup"])
[1105]1015                fields.update(newfields)
1016                self.doModify(dn, fields)
1017                mustadd = 0
[1510]1018            else :
1019                message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name
1020                if action.lower() == "warn" :   
[1584]1021                    self.tool.printInfo("%s. A new entry will be created instead." % message, "warn")
[1510]1022                else : # 'fail' or incorrect setting
1023                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
[1105]1024               
1025        if mustadd :
[2652]1026            fields = { self.info["grouprdn"] : gname,
[1105]1027                       "objectClass" : ["pykotaObject", "pykotaGroup"],
[2652]1028                       "cn" : gname,
[1105]1029                     } 
1030            fields.update(newfields)         
[2652]1031            dn = "%s=%s,%s" % (self.info["grouprdn"], gname, self.info["groupbase"])
[1105]1032            self.doAdd(dn, fields)
[2773]1033        group.isDirty = False
1034        return None # the entry created doesn't need further modification
[1016]1035       
[1041]1036    def addUserToGroup(self, user, group) :   
1037        """Adds an user to a group."""
[1141]1038        if user.Name not in [u.Name for u in self.getGroupMembers(group)] :
[1041]1039            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
1040            if result :
1041                fields = result[0][1]
[1070]1042                if not fields.has_key(self.info["groupmembers"]) :
1043                    fields[self.info["groupmembers"]] = []
[3294]1044                fields[self.info["groupmembers"]].append(unicodeToDatabase(user.Name))
[1041]1045                self.doModify(group.ident, fields)
1046                group.Members.append(user)
1047               
[2706]1048    def delUserFromGroup(self, user, group) :   
1049        """Removes an user from a group."""
[2753]1050        if user.Name in [u.Name for u in self.getGroupMembers(group)] :
[2750]1051            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)
1052            if result :
1053                fields = result[0][1]
1054                if not fields.has_key(self.info["groupmembers"]) :
1055                    fields[self.info["groupmembers"]] = []
1056                try :   
[3294]1057                    fields[self.info["groupmembers"]].remove(unicodeToDatabase(user.Name))
[2750]1058                except ValueError :
1059                    pass # TODO : Strange, shouldn't it be there ?
1060                else :
1061                    self.doModify(group.ident, fields)
1062                    group.Members.remove(user)
[2706]1063               
[2749]1064    def addUserPQuota(self, upq) :
[1041]1065        """Initializes a user print quota on a printer."""
[2749]1066        # first check if an entry already exists
1067        oldentry = self.getUserPQuota(upq.User, upq.Printer)
1068        if oldentry.Exists :
1069            return oldentry # we return the existing entry
[1030]1070        uuid = self.genUUID()
[3294]1071        uname = unicodeToDatabase(upq.User.Name)
1072        pname = unicodeToDatabase(upq.Printer.Name)
[1041]1073        fields = { "cn" : uuid,
1074                   "objectClass" : ["pykotaObject", "pykotaUserPQuota"],
[2652]1075                   "pykotaUserName" : uname,
1076                   "pykotaPrinterName" : pname,
[2749]1077                   "pykotaSoftLimit" : str(upq.SoftLimit),
1078                   "pykotaHardLimit" : str(upq.HardLimit),
1079                   "pykotaDateLimit" : str(upq.DateLimit),
1080                   "pykotaPageCounter" : str(upq.PageCounter or 0),
1081                   "pykotaLifePageCounter" : str(upq.LifePageCounter or 0),
1082                   "pykotaWarnCount" : str(upq.WarnCount or 0),
1083                   "pykotaMaxJobSize" : str(upq.MaxJobSize or 0),
[1030]1084                 } 
[1969]1085        if self.info["userquotabase"].lower() == "user" :
[2749]1086            dn = "cn=%s,%s" % (uuid, upq.User.ident)
[1969]1087        else :   
1088            dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
[1031]1089        self.doAdd(dn, fields)
[2749]1090        upq.isDirty = False
1091        return None # the entry created doesn't need further modification
[1016]1092       
[2749]1093    def addGroupPQuota(self, gpq) :
[1041]1094        """Initializes a group print quota on a printer."""
[2749]1095        oldentry = self.getGroupPQuota(gpq.Group, gpq.Printer)
1096        if oldentry.Exists :
1097            return oldentry # we return the existing entry
[1030]1098        uuid = self.genUUID()
[3294]1099        gname = unicodeToDatabase(gpq.Group.Name)
1100        pname = unicodeToDatabase(gpq.Printer.Name)
[1041]1101        fields = { "cn" : uuid,
1102                   "objectClass" : ["pykotaObject", "pykotaGroupPQuota"],
[2652]1103                   "pykotaGroupName" : gname,
1104                   "pykotaPrinterName" : pname,
[1030]1105                   "pykotaDateLimit" : "None",
1106                 } 
[1969]1107        if self.info["groupquotabase"].lower() == "group" :
[2749]1108            dn = "cn=%s,%s" % (uuid, gpq.Group.ident)
[1969]1109        else :   
1110            dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
[1031]1111        self.doAdd(dn, fields)
[2749]1112        gpq.isDirty = False
1113        return None # the entry created doesn't need further modification
[1016]1114       
[2686]1115    def savePrinter(self, printer) :   
1116        """Saves the printer to the database in a single operation."""
[1041]1117        fields = {
[2686]1118                   "pykotaPassThrough" : (printer.PassThrough and "t") or "f",
[2768]1119                   "pykotaMaxJobSize" : str(printer.MaxJobSize or 0),
[3294]1120                   "description" : unicodeToDatabase(printer.Description or ""),
[2768]1121                   "pykotaPricePerPage" : str(printer.PricePerPage or 0.0),
1122                   "pykotaPricePerJob" : str(printer.PricePerJob or 0.0),
[1041]1123                 }
1124        self.doModify(printer.ident, fields)
[1016]1125       
[2706]1126    def saveUser(self, user) :
1127        """Saves the user to the database in a single operation."""
1128        newfields = {
[3363]1129                       "pykotaLimitBy" : unicodeToDatabase(user.LimitBy or u"quota"),
[3294]1130                       "description" : unicodeToDatabase(user.Description or ""), 
[2938]1131                       self.info["usermail"] : user.Email or "",
[2706]1132                    }   
1133        self.doModify(user.ident, newfields)
[2054]1134       
[2707]1135        newfields = { "pykotaBalance" : str(user.AccountBalance or 0.0),
1136                      "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
[3086]1137                      "pykotaOverCharge" : str(user.OverCharge),
[2707]1138                    }
1139        self.doModify(user.idbalance, newfields)
1140       
[2706]1141    def saveGroup(self, group) :
1142        """Saves the group to the database in a single operation."""
1143        newfields = {
[3363]1144                       "pykotaLimitBy" : unicodeToDatabase(group.LimitBy or u"quota"),
[3294]1145                       "description" : unicodeToDatabase(group.Description or ""), 
[2706]1146                    }   
1147        self.doModify(group.ident, newfields)
[1016]1148       
[1041]1149    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :   
1150        """Sets the date limit permanently for a user print quota."""
[1031]1151        fields = {
[2880]1152                   "pykotaDateLimit" : str(datelimit),
[1031]1153                 }
[1041]1154        return self.doModify(userpquota.ident, fields)
1155           
1156    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :   
1157        """Sets the date limit permanently for a group print quota."""
[1031]1158        fields = {
[2880]1159                   "pykotaDateLimit" : str(datelimit),
[1031]1160                 }
[1041]1161        return self.doModify(grouppquota.ident, fields)
[1016]1162       
[1269]1163    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :   
1164        """Increase page counters for a user print quota."""
1165        fields = {
1166                   "pykotaPageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
1167                   "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
1168                 }
1169        return self.doModify(userpquota.ident, fields)         
1170       
1171    def decreaseUserAccountBalance(self, user, amount) :   
1172        """Decreases user's account balance from an amount."""
1173        fields = {
1174                   "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float },
1175                 }
[1356]1176        return self.doModify(user.idbalance, fields, flushcache=1)         
[1269]1177       
[2452]1178    def writeNewPayment(self, user, amount, comment="") :
[1522]1179        """Adds a new payment to the payments history."""
1180        payments = []
1181        for payment in user.Payments :
[3294]1182            payments.append("%s # %s # %s" % (payment[0], str(payment[1]), base64.encodestring(unicodeToDatabase(payment[2])).strip()))
1183        payments.append("%s # %s # %s" % (str(DateTime.now()), str(amount), base64.encodestring(unicodeToDatabase(comment)).strip()))
[1522]1184        fields = {
1185                   "pykotaPayments" : payments,
1186                 }
1187        return self.doModify(user.idbalance, fields)         
1188       
[1203]1189    def writeLastJobSize(self, lastjob, jobsize, jobprice) :       
[1041]1190        """Sets the last job's size permanently."""
1191        fields = {
1192                   "pykotaJobSize" : str(jobsize),
[1203]1193                   "pykotaJobPrice" : str(jobprice),
[1041]1194                 }
1195        self.doModify(lastjob.ident, fields)         
1196       
[2455]1197    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None, precomputedsize=None, precomputedprice=None) :
[1041]1198        """Adds a job in a printer's history."""
[3294]1199        uname = unicodeToDatabase(user.Name)
1200        pname = unicodeToDatabase(printer.Name)
[1149]1201        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1202            uuid = self.genUUID()
1203            dn = "cn=%s,%s" % (uuid, self.info["jobbase"])
1204        else :   
1205            uuid = printer.LastJob.ident[3:].split(",")[0]
1206            dn = printer.LastJob.ident
[1875]1207        if self.privacy :   
1208            # For legal reasons, we want to hide the title, filename and options
[3364]1209            title = filename = options = u"hidden"
[1032]1210        fields = {
1211                   "objectClass" : ["pykotaObject", "pykotaJob"],
1212                   "cn" : uuid,
[2652]1213                   "pykotaUserName" : uname,
1214                   "pykotaPrinterName" : pname,
[3364]1215                   "pykotaJobId" : unicodeToDatabase(jobid),
[1032]1216                   "pykotaPrinterPageCounter" : str(pagecounter),
[3364]1217                   "pykotaAction" : unicodeToDatabase(action),
[3294]1218                   "pykotaFileName" : ((filename is None) and "None") or unicodeToDatabase(filename), 
1219                   "pykotaTitle" : ((title is None) and "None") or unicodeToDatabase(title), 
[1200]1220                   "pykotaCopies" : str(copies), 
[3294]1221                   "pykotaOptions" : ((options is None) and "None") or unicodeToDatabase(options), 
[1502]1222                   "pykotaHostName" : str(clienthost), 
[1520]1223                   "pykotaJobSizeBytes" : str(jobsizebytes),
[3364]1224                   "pykotaMD5Sum" : unicodeToDatabase(jobmd5sum),
[2217]1225                   "pykotaPages" : jobpages,            # don't add this attribute if it is not set, so no string conversion
[3294]1226                   "pykotaBillingCode" : unicodeToDatabase(jobbilling), # don't add this attribute if it is not set, so no string conversion
[2455]1227                   "pykotaPrecomputedJobSize" : str(precomputedsize),
[2456]1228                   "pykotaPrecomputedJobPrice" : str(precomputedprice),
[1032]1229                 }
[1149]1230        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1231            if jobsize is not None :         
[1203]1232                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
[1149]1233            self.doAdd(dn, fields)
1234        else :   
1235            # here we explicitly want to reset jobsize to 'None' if needed
[1203]1236            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
[1149]1237            self.doModify(dn, fields)
1238           
[1041]1239        if printer.LastJob.Exists :
[1032]1240            fields = {
1241                       "pykotaLastJobIdent" : uuid,
1242                     }
[1041]1243            self.doModify(printer.LastJob.lastjobident, fields)         
[1032]1244        else :   
1245            lastjuuid = self.genUUID()
[1067]1246            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
[1032]1247            fields = {
1248                       "objectClass" : ["pykotaObject", "pykotaLastJob"],
1249                       "cn" : lastjuuid,
[2652]1250                       "pykotaPrinterName" : pname,
[1032]1251                       "pykotaLastJobIdent" : uuid,
1252                     } 
1253            self.doAdd(lastjdn, fields)         
[1041]1254           
[2735]1255    def saveUserPQuota(self, userpquota) :
1256        """Saves an user print quota entry."""
[1041]1257        fields = { 
[2735]1258                   "pykotaSoftLimit" : str(userpquota.SoftLimit),
1259                   "pykotaHardLimit" : str(userpquota.HardLimit),
1260                   "pykotaDateLimit" : str(userpquota.DateLimit),
[2749]1261                   "pykotaWarnCount" : str(userpquota.WarnCount or 0),
1262                   "pykotaPageCounter" : str(userpquota.PageCounter or 0),
1263                   "pykotaLifePageCounter" : str(userpquota.LifePageCounter or 0),
1264                   "pykotaMaxJobSize" : str(userpquota.MaxJobSize or 0),
[1041]1265                 }
1266        self.doModify(userpquota.ident, fields)
1267       
[2054]1268    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
1269        """Sets the warn counter value for a user quota."""
1270        fields = { 
1271                   "pykotaWarnCount" : str(warncount or 0),
1272                 }
1273        self.doModify(userpquota.ident, fields)
1274       
1275    def increaseUserPQuotaWarnCount(self, userpquota) :
1276        """Increases the warn counter value for a user quota."""
1277        fields = {
1278                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
1279                 }
1280        return self.doModify(userpquota.ident, fields)         
1281       
[2735]1282    def saveGroupPQuota(self, grouppquota) :
1283        """Saves a group print quota entry."""
[1041]1284        fields = { 
[2735]1285                   "pykotaSoftLimit" : str(grouppquota.SoftLimit),
1286                   "pykotaHardLimit" : str(grouppquota.HardLimit),
1287                   "pykotaDateLimit" : str(grouppquota.DateLimit),
[2798]1288                   "pykotaMaxJobSize" : str(grouppquota.MaxJobSize or 0),
[1041]1289                 }
1290        self.doModify(grouppquota.ident, fields)
1291           
[1258]1292    def writePrinterToGroup(self, pgroup, printer) :
1293        """Puts a printer into a printer group."""
[1259]1294        if printer.ident not in pgroup.uniqueMember :
[1269]1295            pgroup.uniqueMember.append(printer.ident)
[1258]1296            fields = {
[1269]1297                       "uniqueMember" : pgroup.uniqueMember
[1258]1298                     } 
1299            self.doModify(pgroup.ident, fields)         
[1274]1300           
[1332]1301    def removePrinterFromGroup(self, pgroup, printer) :
1302        """Removes a printer from a printer group."""
1303        try :
1304            pgroup.uniqueMember.remove(printer.ident)
1305        except ValueError :   
1306            pass
1307        else :   
1308            fields = {
1309                       "uniqueMember" : pgroup.uniqueMember,
1310                     } 
1311            self.doModify(pgroup.ident, fields)         
1312           
[3056]1313    def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) :
[2266]1314        """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results."""
[1274]1315        precond = "(objectClass=pykotaJob)"
1316        where = []
[2222]1317        if user is not None :
[3294]1318            where.append("(pykotaUserName=%s)" % unicodeToDatabase(user.Name))
[2222]1319        if printer is not None :
[3294]1320            where.append("(pykotaPrinterName=%s)" % unicodeToDatabase(printer.Name))
[1502]1321        if hostname is not None :
1322            where.append("(pykotaHostName=%s)" % hostname)
[2218]1323        if billingcode is not None :
[3294]1324            where.append("(pykotaBillingCode=%s)" % unicodeToDatabase(billingcode))
[3056]1325        if jobid is not None :
[3294]1326            where.append("(pykotaJobId=%s)" % jobid) # TODO : jobid is text, so unicodeToDatabase(jobid) but do all of them as well.
[1274]1327        if where :   
1328            where = "(&%s)" % "".join([precond] + where)
1329        else :   
1330            where = precond
1331        jobs = []   
[2211]1332        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
1333                                               "pykotaHostName", 
1334                                               "pykotaUserName", 
1335                                               "pykotaPrinterName", 
1336                                               "pykotaJobId", 
1337                                               "pykotaPrinterPageCounter", 
1338                                               "pykotaAction", 
1339                                               "pykotaJobSize", 
1340                                               "pykotaJobPrice", 
1341                                               "pykotaFileName", 
1342                                               "pykotaTitle", 
1343                                               "pykotaCopies", 
1344                                               "pykotaOptions", 
1345                                               "pykotaBillingCode", 
1346                                               "pykotaPages", 
1347                                               "pykotaMD5Sum", 
[2455]1348                                               "pykotaPrecomputedJobSize",
1349                                               "pykotaPrecomputedJobPrice",
[2211]1350                                               "createTimestamp" ], 
1351                                      base=self.info["jobbase"])
[1274]1352        if result :
1353            for (ident, fields) in result :
1354                job = StorageJob(self)
1355                job.ident = ident
[3364]1356                job.JobId = databaseToUnicode(fields.get("pykotaJobId")[0])
[1392]1357                job.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
[1601]1358                try :
1359                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
1360                except ValueError :   
1361                    job.JobSize = None
1362                try :   
1363                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
1364                except ValueError :
1365                    job.JobPrice = None
[3364]1366                job.JobAction = databaseToUnicode(fields.get("pykotaAction", [""])[0])
[3294]1367                job.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0]) 
1368                job.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0]) 
[1274]1369                job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
[3294]1370                job.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0]) 
[3364]1371                job.JobHostName = databaseToUnicode(fields.get("pykotaHostName", [""])[0])
[1520]1372                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
[3294]1373                job.JobBillingCode = databaseToUnicode(fields.get("pykotaBillingCode", [None])[0])
[3364]1374                job.JobMD5Sum = databaseToUnicode(fields.get("pykotaMD5Sum", [None])[0])
[2211]1375                job.JobPages = fields.get("pykotaPages", [""])[0]
[2455]1376                try :
1377                    job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
1378                except ValueError :   
1379                    job.PrecomputedJobSize = None
1380                try :   
1381                    job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
1382                except ValueError :
1383                    job.PrecomputedJobPrice = None
[3364]1384                if job.JobTitle == job.JobFileName == job.JobOptions == u"hidden" :
[2287]1385                    (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
[2538]1386                date = fields.get("createTimestamp", ["19700101000000Z"])[0] # It's in UTC !
1387                mxtime = DateTime.strptime(date[:14], "%Y%m%d%H%M%S").localtime()
[2953]1388                job.JobDate = mxtime.strftime("%Y-%m-%d %H:%M:%S")
[2266]1389                if ((start is None) and (end is None)) or \
1390                   ((start is None) and (job.JobDate <= end)) or \
1391                   ((end is None) and (job.JobDate >= start)) or \
1392                   ((job.JobDate >= start) and (job.JobDate <= end)) :
[3294]1393                    job.UserName = databaseToUnicode(fields.get("pykotaUserName")[0])
1394                    job.PrinterName = databaseToUnicode(fields.get("pykotaPrinterName")[0])
[2950]1395                    job.Exists = True
[1274]1396                    jobs.append(job)
[1874]1397            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))       
[1274]1398            if limit :   
1399                jobs = jobs[:int(limit)]
1400        return jobs
[1258]1401       
[1041]1402    def deleteUser(self, user) :   
1403        """Completely deletes an user from the Quota Storage."""
[3294]1404        uname = unicodeToDatabase(user.Name)
[1692]1405        todelete = []   
[2652]1406        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % uname, base=self.info["jobbase"])
[1041]1407        for (ident, fields) in result :
[1692]1408            todelete.append(ident)
[1998]1409        if self.info["userquotabase"].lower() == "user" :
1410            base = self.info["userbase"]
1411        else :
1412            base = self.info["userquotabase"]
[2652]1413        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s))" % uname, \
1414                                  ["pykotaPrinterName", "pykotaUserName"], \
1415                                  base=base)
[1041]1416        for (ident, fields) in result :
[1692]1417            # ensure the user print quota entry will be deleted
1418            todelete.append(ident)
1419           
1420            # if last job of current printer was printed by the user
1421            # to delete, we also need to delete the printer's last job entry.
[3294]1422            printer = self.getPrinter(databaseToUnicode(fields["pykotaPrinterName"][0]))
[1692]1423            if printer.LastJob.UserName == user.Name :
1424                todelete.append(printer.LastJob.lastjobident)
1425           
1426        for ident in todelete :   
[1041]1427            self.doDelete(ident)
[1692]1428           
[1041]1429        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)   
[1032]1430        if result :
[1041]1431            fields = result[0][1]
1432            for k in fields.keys() :
1433                if k.startswith("pykota") :
1434                    del fields[k]
1435                elif k.lower() == "objectclass" :   
1436                    todelete = []
1437                    for i in range(len(fields[k])) :
1438                        if fields[k][i].startswith("pykota") : 
1439                            todelete.append(i)
1440                    todelete.sort()       
1441                    todelete.reverse()
1442                    for i in todelete :
1443                        del fields[k][i]
[1119]1444            if fields.get("objectClass") or fields.get("objectclass") :
[1041]1445                self.doModify(user.ident, fields, ignoreold=0)       
1446            else :   
1447                self.doDelete(user.ident)
[2652]1448        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % \
1449                                   uname, \
1450                                   ["pykotaUserName"], \
1451                                   base=self.info["balancebase"])
[1041]1452        for (ident, fields) in result :
1453            self.doDelete(ident)
1454       
1455    def deleteGroup(self, group) :   
1456        """Completely deletes a group from the Quota Storage."""
[3294]1457        gname = unicodeToDatabase(group.Name)
[1998]1458        if self.info["groupquotabase"].lower() == "group" :
1459            base = self.info["groupbase"]
1460        else :
1461            base = self.info["groupquotabase"]
[2652]1462        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s))" % \
1463                                  gname, \
1464                                  ["pykotaGroupName"], \
1465                                  base=base)
[1041]1466        for (ident, fields) in result :
1467            self.doDelete(ident)
1468        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
[1016]1469        if result :
[1027]1470            fields = result[0][1]
[1041]1471            for k in fields.keys() :
1472                if k.startswith("pykota") :
1473                    del fields[k]
1474                elif k.lower() == "objectclass" :   
1475                    todelete = []
1476                    for i in range(len(fields[k])) :
1477                        if fields[k][i].startswith("pykota") : 
1478                            todelete.append(i)
1479                    todelete.sort()       
1480                    todelete.reverse()
1481                    for i in todelete :
1482                        del fields[k][i]
[1119]1483            if fields.get("objectClass") or fields.get("objectclass") :
[1041]1484                self.doModify(group.ident, fields, ignoreold=0)       
1485            else :   
1486                self.doDelete(group.ident)
[1330]1487               
[2765]1488    def deleteManyBillingCodes(self, billingcodes) :
1489        """Deletes many billing codes."""
1490        for bcode in billingcodes :
[2763]1491            bcode.delete()
1492       
[2765]1493    def deleteManyUsers(self, users) :       
1494        """Deletes many users."""
1495        for user in users :
[2763]1496            user.delete()
1497           
[2765]1498    def deleteManyGroups(self, groups) :       
1499        """Deletes many groups."""
1500        for group in groups :
[2763]1501            group.delete()
1502       
[2765]1503    def deleteManyPrinters(self, printers) :       
1504        """Deletes many printers."""
1505        for printer in printers :
[2763]1506            printer.delete()
1507       
[2749]1508    def deleteManyUserPQuotas(self, printers, users) :       
1509        """Deletes many user print quota entries."""
1510        # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !)
1511        for printer in printers :
1512            for user in users :
1513                upq = self.getUserPQuota(user, printer)
1514                if upq.Exists :
1515                    upq.delete()
1516           
1517    def deleteManyGroupPQuotas(self, printers, groups) :
1518        """Deletes many group print quota entries."""
1519        # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !)
1520        for printer in printers :
1521            for group in groups :
1522                gpq = self.getGroupPQuota(group, printer)
1523                if gpq.Exists :
1524                    gpq.delete()
1525               
[2717]1526    def deleteUserPQuota(self, upquota) :   
1527        """Completely deletes an user print quota entry from the database."""
[3294]1528        uname = unicodeToDatabase(upquota.User.Name)
1529        pname = unicodeToDatabase(upquota.Printer.Name)
[2717]1530        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s)(pykotaPrinterName=%s))" \
1531                                   % (uname, pname), \
1532                                   base=self.info["jobbase"])
1533        for (ident, fields) in result :
1534            self.doDelete(ident)
[2718]1535        if upquota.Printer.LastJob.UserName == upquota.User.Name :
1536            self.doDelete(upquota.Printer.LastJob.lastjobident)
[2717]1537        self.doDelete(upquota.ident)
1538       
1539    def deleteGroupPQuota(self, gpquota) :   
1540        """Completely deletes a group print quota entry from the database."""
1541        self.doDelete(gpquota.ident)
1542               
[1330]1543    def deletePrinter(self, printer) :   
[2358]1544        """Completely deletes a printer from the Quota Storage."""
[3294]1545        pname = unicodeToDatabase(printer.Name)
[2652]1546        result = self.doSearch("(&(objectClass=pykotaLastJob)(pykotaPrinterName=%s))" % pname, base=self.info["lastjobbase"])
[1330]1547        for (ident, fields) in result :
1548            self.doDelete(ident)
[2652]1549        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaPrinterName=%s))" % pname, base=self.info["jobbase"])
[1330]1550        for (ident, fields) in result :
1551            self.doDelete(ident)
[1998]1552        if self.info["groupquotabase"].lower() == "group" :
1553            base = self.info["groupbase"]
1554        else :
1555            base = self.info["groupquotabase"]
[2652]1556        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % pname, base=base)
[1330]1557        for (ident, fields) in result :
1558            self.doDelete(ident)
[1998]1559        if self.info["userquotabase"].lower() == "user" :
1560            base = self.info["userbase"]
1561        else :
1562            base = self.info["userquotabase"]
[2652]1563        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % pname, base=base)
[1330]1564        for (ident, fields) in result :
1565            self.doDelete(ident)
1566        for parent in self.getParentPrinters(printer) : 
[1332]1567            try :
1568                parent.uniqueMember.remove(printer.ident)
1569            except ValueError :   
1570                pass
1571            else :   
1572                fields = {
1573                           "uniqueMember" : parent.uniqueMember,
1574                         } 
1575                self.doModify(parent.ident, fields)         
[1330]1576        self.doDelete(printer.ident)   
[1754]1577       
[2358]1578    def deleteBillingCode(self, code) :
1579        """Deletes a billing code from the Quota Storage (no entries are deleted from the history)"""
1580        self.doDelete(code.ident)
1581       
[3167]1582    def sortRecords(self, fields, records, default, ordering) :     
1583        """Sort records based on list of fields prefixed with '+' (ASC) or '-' (DESC)."""
1584        fieldindexes = {}
1585        for i in range(len(fields)) :
1586            fieldindexes[fields[i]] = i
1587        if not ordering :   
1588            ordering = default
1589        orderby = []   
1590        for orderkey in ordering :
[3168]1591            # Create ordering hints, ignoring unknown fields
[3167]1592            if orderkey.startswith("-") :
[3168]1593                index = fieldindexes.get(orderkey[1:])
1594                if index is not None :
1595                    orderby.append((-1, index))
[3167]1596            elif orderkey.startswith("+") :
[3168]1597                index = fieldindexes.get(orderkey[1:])
1598                if index is not None :
1599                    orderby.append((+1, index))
[3167]1600            else :   
[3168]1601                index = fieldindexes.get(orderkey)
1602                if index is not None :
1603                    orderby.append((+1, index))
[3167]1604               
1605        def compare(x, y, orderby=orderby) :   
1606            """Compares two records."""
1607            i = 0
1608            nbkeys = len(orderby)
1609            while i < nbkeys :
1610                (sign, index) = orderby[i]
1611                result = cmp(x[i], y[i])
1612                if not result :
1613                    i += 1
1614                else :   
1615                    return sign * result
1616            return 0 # identical keys       
1617           
1618        records.sort(compare)
1619        return records
1620       
[3165]1621    def extractPrinters(self, extractonly={}, ordering=[]) :
[1754]1622        """Extracts all printer records."""
[1993]1623        pname = extractonly.get("printername")
1624        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1625        if entries :
[3167]1626            fields = ("dn", "printername", "priceperpage", "priceperjob", "description", "maxjobsize", "passthrough")
1627            result = []
[1754]1628            for entry in entries :
[2459]1629                if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") :
1630                    passthrough = "t"
1631                else :   
1632                    passthrough = "f"
1633                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description, entry.MaxJobSize, passthrough))
[3167]1634            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
[1754]1635       
[3165]1636    def extractUsers(self, extractonly={}, ordering=[]) :
[1754]1637        """Extracts all user records."""
[1993]1638        uname = extractonly.get("username")
1639        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
[1754]1640        if entries :
[3167]1641            fields = ("dn", "username", "balance", "lifetimepaid", "limitby", "email", "description", "overcharge")
1642            result = []
[1754]1643            for entry in entries :
[3086]1644                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email, entry.Description, entry.OverCharge))
[3167]1645            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[1754]1646       
[3165]1647    def extractBillingcodes(self, extractonly={}, ordering=[]) :
[2358]1648        """Extracts all billing codes records."""
1649        billingcode = extractonly.get("billingcode")
1650        entries = [b for b in [self.getBillingCode(label) for label in self.getAllBillingCodes(billingcode)] if b.Exists]
1651        if entries :
[3167]1652            fields = ("dn", "billingcode", "balance", "pagecounter", "description")
1653            result = []
[2358]1654            for entry in entries :
1655                result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description))
[3167]1656            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[2358]1657       
[3165]1658    def extractGroups(self, extractonly={}, ordering=[]) :
[1754]1659        """Extracts all group records."""
[1993]1660        gname = extractonly.get("groupname")
1661        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
[1754]1662        if entries :
[3167]1663            fields = ("dn", "groupname", "limitby", "balance", "lifetimepaid", "description")
1664            result = []
[1754]1665            for entry in entries :
[2721]1666                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid, entry.Description))
[3167]1667            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[1754]1668       
[3165]1669    def extractPayments(self, extractonly={}, ordering=[]) :
[1754]1670        """Extracts all payment records."""
[3142]1671        startdate = extractonly.get("start")
1672        enddate = extractonly.get("end")
1673        (startdate, enddate) = self.cleanDates(startdate, enddate)
[1993]1674        uname = extractonly.get("username")
1675        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
[1765]1676        if entries :
[3167]1677            fields = ("username", "amount", "date", "description")
1678            result = []
[1765]1679            for entry in entries :
[2452]1680                for (date, amount, description) in entry.Payments :
[3142]1681                    if ((startdate is None) and (enddate is None)) or \
1682                       ((startdate is None) and (date <= enddate)) or \
1683                       ((enddate is None) and (date >= startdate)) or \
1684                       ((date >= startdate) and (date <= enddate)) :
1685                        result.append((entry.Name, amount, date, description))
[3167]1686            return [fields] + self.sortRecords(fields, result, ["+date"], ordering)
[1754]1687       
[3165]1688    def extractUpquotas(self, extractonly={}, ordering=[]) :
[1754]1689        """Extracts all userpquota records."""
[1993]1690        pname = extractonly.get("printername")
1691        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1692        if entries :
[3167]1693            fields = ("username", "printername", "dn", "userdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit")
1694            result = []
[2040]1695            uname = extractonly.get("username")
[1764]1696            for entry in entries :
[2041]1697                for (user, userpquota) in self.getPrinterUsersAndQuotas(entry, names=[uname or "*"]) :
1698                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit))
[3167]1699            return [fields] + self.sortRecords(fields, result, ["+userdn"], ordering)
[1754]1700       
[3165]1701    def extractGpquotas(self, extractonly={}, ordering=[]) :
[1754]1702        """Extracts all grouppquota records."""
[1993]1703        pname = extractonly.get("printername")
1704        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1705        if entries :
[3167]1706            fields = ("groupname", "printername", "dn", "groupdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit")
1707            result = []
[1993]1708            gname = extractonly.get("groupname")
[1764]1709            for entry in entries :
[2042]1710                for (group, grouppquota) in self.getPrinterGroupsAndQuotas(entry, names=[gname or "*"]) :
1711                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
[3167]1712            return [fields] + self.sortRecords(fields, result, ["+groupdn"], ordering)
[1754]1713       
[3165]1714    def extractUmembers(self, extractonly={}, ordering=[]) :
[1754]1715        """Extracts all user groups members."""
[1993]1716        gname = extractonly.get("groupname")
1717        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
[1754]1718        if entries :
[3167]1719            fields = ("groupname", "username", "groupdn", "userdn")
1720            result = []
[1993]1721            uname = extractonly.get("username")
[1754]1722            for entry in entries :
1723                for member in entry.Members :
[1993]1724                    if (uname is None) or (member.Name == uname) :
1725                        result.append((entry.Name, member.Name, entry.ident, member.ident))
[3167]1726            return [fields] + self.sortRecords(fields, result, ["+groupdn", "+userdn"], ordering)
[1754]1727               
[3165]1728    def extractPmembers(self, extractonly={}, ordering=[]) :
[1754]1729        """Extracts all printer groups members."""
[1993]1730        pname = extractonly.get("printername")
1731        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1732        if entries :
[3167]1733            fields = ("pgroupname", "printername", "pgroupdn", "printerdn")
1734            result = []
[1993]1735            pgname = extractonly.get("pgroupname")
[1754]1736            for entry in entries :
1737                for parent in self.getParentPrinters(entry) :
[1993]1738                    if (pgname is None) or (parent.Name == pgname) :
1739                        result.append((parent.Name, entry.Name, parent.ident, entry.ident))
[3167]1740            return [fields] + self.sortRecords(fields, result, ["+pgroupdn", "+printerdn"], ordering)
[1754]1741       
[3165]1742    def extractHistory(self, extractonly={}, ordering=[]) :
[1754]1743        """Extracts all jobhistory records."""
[1993]1744        uname = extractonly.get("username")
1745        if uname :
1746            user = self.getUser(uname)
1747        else :   
1748            user = None
1749        pname = extractonly.get("printername")
1750        if pname :
1751            printer = self.getPrinter(pname)
1752        else :   
1753            printer = None
[2266]1754        startdate = extractonly.get("start")
1755        enddate = extractonly.get("end")
1756        (startdate, enddate) = self.cleanDates(startdate, enddate)
[3091]1757        entries = self.retrieveHistory(user, printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), jobid=extractonly.get("jobid"), limit=None, start=startdate, end=enddate)
[1754]1758        if entries :
[3167]1759            fields = ("username", "printername", "dn", "jobid", "pagecounter", "jobsize", "action", "jobdate", "filename", "title", "copies", "options", "jobprice", "hostname", "jobsizebytes", "md5sum", "pages", "billingcode", "precomputedjobsize", "precomputedjobprice")
1760            result = []
[1754]1761            for entry in entries :
[2455]1762                result.append((entry.UserName, entry.PrinterName, entry.ident, entry.JobId, entry.PrinterPageCounter, entry.JobSize, entry.JobAction, entry.JobDate, entry.JobFileName, entry.JobTitle, entry.JobCopies, entry.JobOptions, entry.JobPrice, entry.JobHostName, entry.JobSizeBytes, entry.JobMD5Sum, entry.JobPages, entry.JobBillingCode, entry.PrecomputedJobSize, entry.PrecomputedJobPrice)) 
[3167]1763            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[2358]1764           
[2372]1765    def getBillingCodeFromBackend(self, label) :
1766        """Extracts billing code information given its label : returns first matching billing code."""
1767        code = StorageBillingCode(self, label)
[3294]1768        ulabel = unicodeToDatabase(label)
[2652]1769        result = self.doSearch("(&(objectClass=pykotaBilling)(pykotaBillingCode=%s))" % \
1770                                  ulabel, \
1771                                  ["pykotaBillingCode", "pykotaBalance", "pykotaPageCounter", "description"], \
1772                                  base=self.info["billingcodebase"])
[2372]1773        if result :
1774            fields = result[0][1]       # take only first matching code, ignore the rest
1775            code.ident = result[0][0]
[3294]1776            code.BillingCode = databaseToUnicode(fields.get("pykotaBillingCode", [ulabel])[0])
[2372]1777            code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1778            code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
[3294]1779            code.Description = databaseToUnicode(fields.get("description", [""])[0]) 
[2950]1780            code.Exists = True
[2372]1781        return code   
[2375]1782       
[2765]1783    def addBillingCode(self, bcode) :
[2375]1784        """Adds a billing code to the quota storage, returns it."""
[2765]1785        oldentry = self.getBillingCode(bcode.BillingCode)
1786        if oldentry.Exists :
1787            return oldentry # we return the existing entry
[2375]1788        uuid = self.genUUID()
1789        dn = "cn=%s,%s" % (uuid, self.info["billingcodebase"])
1790        fields = { "objectClass" : ["pykotaObject", "pykotaBilling"],
1791                   "cn" : uuid,
[3294]1792                   "pykotaBillingCode" : unicodeToDatabase(bcode.BillingCode),
[2765]1793                   "pykotaPageCounter" : str(bcode.PageCounter or 0),
1794                   "pykotaBalance" : str(bcode.Balance or 0.0),
[3294]1795                   "description" : unicodeToDatabase(bcode.Description or ""), 
[2375]1796                 } 
1797        self.doAdd(dn, fields)
[2765]1798        bcode.isDirty = False
1799        return None # the entry created doesn't need further modification
[2375]1800       
[2765]1801    def saveBillingCode(self, bcode) :
[2375]1802        """Sets the new description for a billing code."""
1803        fields = {
[3294]1804                   "description" : unicodeToDatabase(bcode.Description or ""), 
[2765]1805                   "pykotaPageCounter" : str(bcode.PageCounter or 0),
1806                   "pykotaBalance" : str(bcode.Balance or 0.0),
[2375]1807                 }
[2765]1808        self.doModify(bcode.ident, fields)
[2358]1809           
[2380]1810    def getMatchingBillingCodes(self, billingcodepattern) :
1811        """Returns the list of all billing codes which match a certain pattern."""
1812        codes = []
[2657]1813        result = self.doSearch("objectClass=pykotaBilling", \
[2380]1814                                ["pykotaBillingCode", "description", "pykotaPageCounter", "pykotaBalance"], \
1815                                base=self.info["billingcodebase"])
1816        if result :
[2657]1817            patterns = billingcodepattern.split(",")
[2776]1818            try :
1819                patdict = {}.fromkeys(patterns)
1820            except AttributeError :   
1821                # Python v2.2 or earlier
1822                patdict = {}
1823                for p in patterns :
1824                    patdict[p] = None
[2380]1825            for (codeid, fields) in result :
[3294]1826                codename = databaseToUnicode(fields.get("pykotaBillingCode", [""])[0])
[2776]1827                if patdict.has_key(codename) or self.tool.matchString(codename, patterns) :
[2657]1828                    code = StorageBillingCode(self, codename)
1829                    code.ident = codeid
1830                    code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1831                    code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
[3294]1832                    code.Description = databaseToUnicode(fields.get("description", [""])[0]) 
[2950]1833                    code.Exists = True
[2657]1834                    codes.append(code)
1835                    self.cacheEntry("BILLINGCODES", code.BillingCode, code)
[2380]1836        return codes       
1837       
[2765]1838    def consumeBillingCode(self, bcode, pagecounter, balance) :
[2384]1839        """Consumes from a billing code."""
1840        fields = {
1841                   "pykotaBalance" : { "operator" : "-", "value" : balance, "convert" : float },
1842                   "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int },
1843                 }
[2765]1844        return self.doModify(bcode.ident, fields)         
[2358]1845
[3056]1846    def refundJob(self, jobident) :   
1847        """Marks a job as refunded in the history."""
1848        fields = {
1849                     "pykotaAction" : "REFUND",
1850                 }   
[3372]1851        self.doModify(jobident, fields)         
[3056]1852       
[2950]1853    def storageUserFromRecord(self, username, record) :
1854        """Returns a StorageUser instance from a database record."""
1855        user = StorageUser(self, username)
1856        user.Exists = True
1857        return user
1858       
1859    def storageGroupFromRecord(self, groupname, record) :
1860        """Returns a StorageGroup instance from a database record."""
1861        group = StorageGroup(self, groupname)
1862        group.Exists = True
1863        return group
1864       
1865    def storagePrinterFromRecord(self, printername, record) :
1866        """Returns a StoragePrinter instance from a database record."""
1867        printer = StoragePrinter(self, printername)
1868        printer.Exists = True
1869        return printer
1870       
1871    def setJobAttributesFromRecord(self, job, record) :   
1872        """Sets the attributes of a job from a database record."""
1873        job.Exists = True
1874       
1875    def storageJobFromRecord(self, record) :
1876        """Returns a StorageJob instance from a database record."""
1877        job = StorageJob(self)
1878        self.setJobAttributesFromRecord(job, record)
1879        return job
1880       
1881    def storageLastJobFromRecord(self, printer, record) :
1882        """Returns a StorageLastJob instance from a database record."""
1883        lastjob = StorageLastJob(self, printer)
1884        self.setJobAttributesFromRecord(lastjob, record)
1885        return lastjob
1886       
1887    def storageUserPQuotaFromRecord(self, user, printer, record) :
1888        """Returns a StorageUserPQuota instance from a database record."""
1889        userpquota = StorageUserPQuota(self, user, printer)
1890        userpquota.Exists = True
1891        return userpquota
1892       
1893    def storageGroupPQuotaFromRecord(self, group, printer, record) :
1894        """Returns a StorageGroupPQuota instance from a database record."""
1895        grouppquota = StorageGroupPQuota(self, group, printer)
1896        grouppquota.Exists = True
1897        return grouppquota
1898       
1899    def storageBillingCodeFromRecord(self, billingcode, record) :
1900        """Returns a StorageBillingCode instance from a database record."""
1901        code = StorageBillingCode(self, billingcode)
1902        code.Exists = True
1903        return code
Note: See TracBrowser for help on using the browser.