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

Revision 2464, 80.1 kB (checked in by jerome, 19 years ago)

Added support to write the maxjobsize and passthrough attributes
for printers.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3#
4# PyKota : Print Quotas for CUPS and LPRng
5#
6# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23#
24
25#
26# My IANA assigned number, for
27# "Conseil Internet & Logiciels Libres, J�me Alet"
28# is 16868. Use this as a base to create the LDAP schema.
29#
30
31import sys
32import os
33import types
34import time
35import md5
36import base64
37from mx import DateTime
38
39from pykota.storage import PyKotaStorageError, BaseStorage, StorageObject, \
40                           StorageUser, StorageGroup, StoragePrinter, \
41                           StorageJob, StorageLastJob, StorageUserPQuota, \
42                           StorageGroupPQuota, StorageBillingCode
43
44try :
45    import ldap
46    import ldap.modlist
47except ImportError :   
48    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0]
49else :   
50    try :
51        from ldap.cidict import cidict
52    except ImportError :   
53        import UserDict
54        sys.stderr.write("ERROR: PyKota requires a newer version of python-ldap. Workaround activated. Please upgrade python-ldap !\n")
55        class cidict(UserDict.UserDict) :
56            pass # Fake it all, and don't care for case insensitivity : users who need it will have to upgrade.
57   
58class Storage(BaseStorage) :
59    def __init__(self, pykotatool, host, dbname, user, passwd) :
60        """Opens the LDAP connection."""
61        self.savedtool = pykotatool
62        self.savedhost = host
63        self.saveddbname = dbname
64        self.saveduser = user
65        self.savedpasswd = passwd
66        self.secondStageInit()
67       
68    def secondStageInit(self) :   
69        """Second stage initialisation."""
70        BaseStorage.__init__(self, self.savedtool)
71        self.info = self.tool.config.getLDAPInfo()
72        message = ""
73        for tryit in range(3) :
74            try :
75                self.tool.logdebug("Trying to open database (host=%s, dbname=%s, user=%s)..." % (self.savedhost, self.saveddbname, self.saveduser))
76                self.database = ldap.initialize(self.savedhost) 
77                if self.info["ldaptls"] :
78                    # we want TLS
79                    ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.info["cacert"])
80                    self.database.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
81                    self.database.start_tls_s()
82                self.database.simple_bind_s(self.saveduser, self.savedpasswd)
83                self.basedn = self.saveddbname
84            except ldap.SERVER_DOWN :   
85                message = "LDAP backend for PyKota seems to be down !"
86                self.tool.printInfo("%s" % message, "error")
87                self.tool.printInfo("Trying again in 2 seconds...", "warn")
88                time.sleep(2)
89            except ldap.LDAPError :   
90                message = "Unable to connect to LDAP server %s as %s." % (self.savedhost, self.saveduser)
91                self.tool.printInfo("%s" % message, "error")
92                self.tool.printInfo("Trying again in 2 seconds...", "warn")
93                time.sleep(2)
94            else :   
95                self.useldapcache = self.tool.config.getLDAPCache()
96                if self.useldapcache :
97                    self.tool.logdebug("Low-Level LDAP Caching enabled.")
98                    self.ldapcache = {} # low-level cache specific to LDAP backend
99                self.closed = 0
100                self.tool.logdebug("Database opened (host=%s, dbname=%s, user=%s)" % (self.savedhost, self.saveddbname, self.saveduser))
101                return # All is fine here.
102        raise PyKotaStorageError, message         
103           
104    def close(self) :   
105        """Closes the database connection."""
106        if not self.closed :
107            self.database.unbind_s()
108            self.closed = 1
109            self.tool.logdebug("Database closed.")
110       
111    def genUUID(self) :   
112        """Generates an unique identifier.
113       
114           TODO : this one is not unique accross several print servers, but should be sufficient for testing.
115        """
116        return md5.md5("%s" % time.time()).hexdigest()
117       
118    def normalizeFields(self, fields) :   
119        """Ensure all items are lists."""
120        for (k, v) in fields.items() :
121            if type(v) not in (types.TupleType, types.ListType) :
122                if not v :
123                    del fields[k]
124                else :   
125                    fields[k] = [ v ]
126        return fields       
127       
128    def beginTransaction(self) :   
129        """Starts a transaction."""
130        self.tool.logdebug("Transaction begins... WARNING : No transactions in LDAP !")
131       
132    def commitTransaction(self) :   
133        """Commits a transaction."""
134        self.tool.logdebug("Transaction committed. WARNING : No transactions in LDAP !")
135       
136    def rollbackTransaction(self) :     
137        """Rollbacks a transaction."""
138        self.tool.logdebug("Transaction aborted. WARNING : No transaction in LDAP !")
139       
140    def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE, flushcache=0) :
141        """Does an LDAP search query."""
142        message = ""
143        for tryit in range(3) :
144            try :
145                base = base or self.basedn
146                if self.useldapcache :
147                    # Here we overwrite the fields the app want, to try and
148                    # retrieve ALL user defined attributes ("*")
149                    # + the createTimestamp attribute, needed by job history
150                    #
151                    # This may not work with all LDAP servers
152                    # but works at least in OpenLDAP (2.1.25)
153                    # and iPlanet Directory Server (5.1 SP3)
154                    fields = ["*", "createTimestamp"]         
155                   
156                if self.useldapcache and (not flushcache) and (scope == ldap.SCOPE_BASE) and self.ldapcache.has_key(base) :
157                    entry = self.ldapcache[base]
158                    self.tool.logdebug("LDAP cache hit %s => %s" % (base, entry))
159                    result = [(base, entry)]
160                else :
161                    self.tool.logdebug("QUERY : Filter : %s, BaseDN : %s, Scope : %s, Attributes : %s" % (key, base, scope, fields))
162                    result = self.database.search_s(base, scope, key, fields)
163            except ldap.NO_SUCH_OBJECT, msg :       
164                raise PyKotaStorageError, (_("Search base %s doesn't seem to exist. Probable misconfiguration. Please double check /etc/pykota/pykota.conf : %s") % (base, msg))
165            except ldap.LDAPError, msg :   
166                message = (_("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)) + " : %s" % str(msg)
167                self.tool.printInfo("LDAP error : %s" % message, "error")
168                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
169                self.close()
170                self.secondStageInit()
171            else :     
172                self.tool.logdebug("QUERY : Result : %s" % result)
173                result = [ (dn, cidict(attrs)) for (dn, attrs) in result ]
174                if self.useldapcache :
175                    for (dn, attributes) in result :
176                        self.tool.logdebug("LDAP cache store %s => %s" % (dn, attributes))
177                        self.ldapcache[dn] = attributes
178                return result
179        raise PyKotaStorageError, message
180           
181    def doAdd(self, dn, fields) :
182        """Adds an entry in the LDAP directory."""
183        fields = self.normalizeFields(cidict(fields))
184        message = ""
185        for tryit in range(3) :
186            try :
187                self.tool.logdebug("QUERY : ADD(%s, %s)" % (dn, str(fields)))
188                entry = ldap.modlist.addModlist(fields)
189                self.tool.logdebug("%s" % entry)
190                self.database.add_s(dn, entry)
191            except ldap.LDAPError, msg :
192                message = (_("Problem adding LDAP entry (%s, %s)") % (dn, str(fields))) + " : %s" % str(msg)
193                self.tool.printInfo("LDAP error : %s" % message, "error")
194                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
195                self.close()
196                self.secondStageInit()
197            else :
198                if self.useldapcache :
199                    self.tool.logdebug("LDAP cache add %s => %s" % (dn, fields))
200                    self.ldapcache[dn] = fields
201                return dn
202        raise PyKotaStorageError, message
203           
204    def doDelete(self, dn) :
205        """Deletes an entry from the LDAP directory."""
206        message = ""
207        for tryit in range(3) :
208            try :
209                self.tool.logdebug("QUERY : Delete(%s)" % dn)
210                self.database.delete_s(dn)
211            except ldap.LDAPError, msg :
212                message = (_("Problem deleting LDAP entry (%s)") % dn) + " : %s" % str(msg)
213                self.tool.printInfo("LDAP error : %s" % message, "error")
214                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
215                self.close()
216                self.secondStageInit()
217            else :   
218                if self.useldapcache :
219                    try :
220                        self.tool.logdebug("LDAP cache del %s" % dn)
221                        del self.ldapcache[dn]
222                    except KeyError :   
223                        pass
224                return       
225        raise PyKotaStorageError, message
226           
227    def doModify(self, dn, fields, ignoreold=1, flushcache=0) :
228        """Modifies an entry in the LDAP directory."""
229        fields = cidict(fields)
230        for tryit in range(3) :
231            try :
232                # TODO : take care of, and update LDAP specific cache
233                if self.useldapcache and not flushcache :
234                    if self.ldapcache.has_key(dn) :
235                        old = self.ldapcache[dn]
236                        self.tool.logdebug("LDAP cache hit %s => %s" % (dn, old))
237                        oldentry = {}
238                        for (k, v) in old.items() :
239                            if k != "createTimestamp" :
240                                oldentry[k] = v
241                    else :   
242                        self.tool.logdebug("LDAP cache miss %s" % dn)
243                        oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)[0][1]
244                else :       
245                    oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE, flushcache=flushcache)[0][1]
246                for (k, v) in fields.items() :
247                    if type(v) == type({}) :
248                        try :
249                            oldvalue = v["convert"](oldentry.get(k, [0])[0])
250                        except ValueError :   
251                            self.tool.logdebug("Error converting %s with %s(%s)" % (oldentry.get(k), k, v))
252                            oldvalue = 0
253                        if v["operator"] == '+' :
254                            newvalue = oldvalue + v["value"]
255                        else :   
256                            newvalue = oldvalue - v["value"]
257                        fields[k] = str(newvalue)
258                fields = self.normalizeFields(fields)
259                self.tool.logdebug("QUERY : Modify(%s, %s ==> %s)" % (dn, oldentry, fields))
260                entry = ldap.modlist.modifyModlist(oldentry, fields, ignore_oldexistent=ignoreold)
261                modentry = []
262                for (mop, mtyp, mval) in entry :
263                    if mtyp and (mtyp.lower() != "createtimestamp") :
264                        modentry.append((mop, mtyp, mval))
265                self.tool.logdebug("MODIFY : %s ==> %s ==> %s" % (fields, entry, modentry))
266                if modentry :
267                    self.database.modify_s(dn, modentry)
268            except ldap.LDAPError, msg :
269                message = (_("Problem modifying LDAP entry (%s, %s)") % (dn, fields)) + " : %s" % str(msg)
270                self.tool.printInfo("LDAP error : %s" % message, "error")
271                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
272                self.close()
273                self.secondStageInit()
274            else :
275                if self.useldapcache :
276                    cachedentry = self.ldapcache[dn]
277                    for (mop, mtyp, mval) in entry :
278                        if mop in (ldap.MOD_ADD, ldap.MOD_REPLACE) :
279                            cachedentry[mtyp] = mval
280                        else :
281                            try :
282                                del cachedentry[mtyp]
283                            except KeyError :   
284                                pass
285                    self.tool.logdebug("LDAP cache update %s => %s" % (dn, cachedentry))
286                return dn
287        raise PyKotaStorageError, message
288           
289    def filterNames(self, records, attribute) :       
290        """Returns a list of 'attribute' from a list of records.
291       
292           Logs any missing attribute.
293        """   
294        result = []
295        for (dn, record) in records :
296            attrval = record.get(attribute, [None])[0]
297            if attrval is None :
298                self.tool.printInfo("Object %s has no %s attribute !" % (dn, attribute), "error")
299            else :   
300                result.append(attrval)
301        return result       
302               
303    def getAllBillingCodes(self, billingcode=None) :   
304        """Extracts all billing codes or only the billing codes matching the optional parameter."""
305        billingcodes = []
306        ldapfilter = "objectClass=pykotaBilling"
307        if billingcode :
308            ldapfilter = "(&(%s)(pykotaBillingCode=%s))" % (ldapfilter, self.userCharsetToDatabase(billingcode))
309        result = self.doSearch(ldapfilter, ["pykotaBillingCode"], base=self.info["billingcodebase"])
310        if result :
311            billingcodes = [self.databaseToUserCharset(bc) for bc in self.filterNames(result, "pykotaBillingCode")]
312        return billingcodes
313       
314    def getAllPrintersNames(self, printername=None) :   
315        """Extracts all printer names or only the printers' names matching the optional parameter."""
316        printernames = []
317        ldapfilter = "objectClass=pykotaPrinter"
318        if printername :
319            ldapfilter = "(&(%s)(pykotaPrinterName=%s))" % (ldapfilter, printername)
320        result = self.doSearch(ldapfilter, ["pykotaPrinterName"], base=self.info["printerbase"])
321        if result :
322            printernames = self.filterNames(result, "pykotaPrinterName")
323        return printernames
324       
325    def getAllUsersNames(self, username=None) :   
326        """Extracts all user names or only the users' names matching the optional parameter."""
327        usernames = []
328        ldapfilter = "objectClass=pykotaAccount"
329        if username :
330            ldapfilter = "(&(%s)(pykotaUserName=%s))" % (ldapfilter, username)
331        result = self.doSearch(ldapfilter, ["pykotaUserName"], base=self.info["userbase"])
332        if result :
333            usernames = self.filterNames(result, "pykotaUserName")
334        return usernames
335       
336    def getAllGroupsNames(self, groupname=None) :   
337        """Extracts all group names or only the groups' names matching the optional parameter."""
338        groupnames = []
339        ldapfilter = "objectClass=pykotaGroup"
340        if groupname :
341            ldapfilter = "(&(%s)(pykotaGroupName=%s))" % (ldapfilter, groupname)
342        result = self.doSearch(ldapfilter, ["pykotaGroupName"], base=self.info["groupbase"])
343        if result :
344            groupnames = self.filterNames(result, "pykotaGroupName")
345        return groupnames
346       
347    def getUserNbJobsFromHistory(self, user) :
348        """Returns the number of jobs the user has in history."""
349        result = self.doSearch("(&(pykotaUserName=%s)(objectClass=pykotaJob))" % user.Name, None, base=self.info["jobbase"])
350        return len(result)
351       
352    def getUserFromBackend(self, username) :   
353        """Extracts user information given its name."""
354        user = StorageUser(self, username)
355        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "pykotaOverCharge"], base=self.info["userbase"])
356        if result :
357            fields = result[0][1]
358            user.ident = result[0][0]
359            user.Name = fields.get("pykotaUserName", [username])[0] 
360            user.Email = fields.get(self.info["usermail"], [None])[0]
361            user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
362            user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
363            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["balancerdn"], username), ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments"], base=self.info["balancebase"])
364            if not result :
365                raise PyKotaStorageError, _("No pykotaAccountBalance object found for user %s. Did you create LDAP entries manually ?") % username
366            else :
367                fields = result[0][1]
368                user.idbalance = result[0][0]
369                user.AccountBalance = fields.get("pykotaBalance")
370                if user.AccountBalance is not None :
371                    if user.AccountBalance[0].upper() == "NONE" :
372                        user.AccountBalance = None
373                    else :   
374                        user.AccountBalance = float(user.AccountBalance[0])
375                user.AccountBalance = user.AccountBalance or 0.0       
376                user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
377                if user.LifeTimePaid is not None :
378                    if user.LifeTimePaid[0].upper() == "NONE" :
379                        user.LifeTimePaid = None
380                    else :   
381                        user.LifeTimePaid = float(user.LifeTimePaid[0])
382                user.LifeTimePaid = user.LifeTimePaid or 0.0       
383                user.Payments = []
384                for payment in fields.get("pykotaPayments", []) :
385                    try :
386                        (date, amount, description) = payment.split(" # ")
387                    except ValueError :
388                        # Payment with no description (old Payment)
389                        (date, amount) = payment.split(" # ")
390                        description = ""
391                    else :   
392                        description = self.databaseToUserCharset(base64.decodestring(description))
393                    user.Payments.append((date, float(amount), description))
394            user.Exists = 1
395        return user
396       
397    def getGroupFromBackend(self, groupname) :   
398        """Extracts group information given its name."""
399        group = StorageGroup(self, groupname)
400        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), ["pykotaGroupName", "pykotaLimitBy"], base=self.info["groupbase"])
401        if result :
402            fields = result[0][1]
403            group.ident = result[0][0]
404            group.Name = fields.get("pykotaGroupName", [groupname])[0] 
405            group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
406            group.AccountBalance = 0.0
407            group.LifeTimePaid = 0.0
408            for member in self.getGroupMembers(group) :
409                if member.Exists :
410                    group.AccountBalance += member.AccountBalance
411                    group.LifeTimePaid += member.LifeTimePaid
412            group.Exists = 1
413        return group
414       
415    def getPrinterFromBackend(self, printername) :       
416        """Extracts printer information given its name : returns first matching printer."""
417        printer = StoragePrinter(self, printername)
418        result = self.doSearch("(&(objectClass=pykotaPrinter)(|(pykotaPrinterName=%s)(%s=%s)))" \
419                      % (printername, self.info["printerrdn"], printername), \
420                        ["pykotaPrinterName", "pykotaPricePerPage", \
421                         "pykotaPricePerJob", "pykotaMaxJobSize", \
422                         "pykotaPassThrough", "uniqueMember", "description"], \
423                      base=self.info["printerbase"])
424        if result :
425            fields = result[0][1]       # take only first matching printer, ignore the rest
426            printer.ident = result[0][0]
427            printer.Name = fields.get("pykotaPrinterName", [printername])[0] 
428            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0])
429            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0])
430            printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
431            printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
432            if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
433                printer.PassThrough = 1
434            else :
435                printer.PassThrough = 0
436            printer.uniqueMember = fields.get("uniqueMember", [])
437            printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
438            printer.Exists = 1
439        return printer   
440       
441    def getUserPQuotaFromBackend(self, user, printer) :       
442        """Extracts a user print quota."""
443        userpquota = StorageUserPQuota(self, user, printer)
444        if printer.Exists and user.Exists :
445            if self.info["userquotabase"].lower() == "user" :
446                base = user.ident
447            else :   
448                base = self.info["userquotabase"]
449            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % (user.Name, printer.Name), ["pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], base=base)
450            if result :
451                fields = result[0][1]
452                userpquota.ident = result[0][0]
453                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
454                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
455                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
456                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
457                if userpquota.SoftLimit is not None :
458                    if userpquota.SoftLimit[0].upper() == "NONE" :
459                        userpquota.SoftLimit = None
460                    else :   
461                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
462                userpquota.HardLimit = fields.get("pykotaHardLimit")
463                if userpquota.HardLimit is not None :
464                    if userpquota.HardLimit[0].upper() == "NONE" :
465                        userpquota.HardLimit = None
466                    elif userpquota.HardLimit is not None :   
467                        userpquota.HardLimit = int(userpquota.HardLimit[0])
468                userpquota.DateLimit = fields.get("pykotaDateLimit")
469                if userpquota.DateLimit is not None :
470                    if userpquota.DateLimit[0].upper() == "NONE" : 
471                        userpquota.DateLimit = None
472                    else :   
473                        userpquota.DateLimit = userpquota.DateLimit[0]
474                userpquota.Exists = 1
475        return userpquota
476       
477    def getGroupPQuotaFromBackend(self, group, printer) :       
478        """Extracts a group print quota."""
479        grouppquota = StorageGroupPQuota(self, group, printer)
480        if group.Exists :
481            if self.info["groupquotabase"].lower() == "group" :
482                base = group.ident
483            else :   
484                base = self.info["groupquotabase"]
485            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % (group.Name, printer.Name), ["pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=base)
486            if result :
487                fields = result[0][1]
488                grouppquota.ident = result[0][0]
489                grouppquota.SoftLimit = fields.get("pykotaSoftLimit")
490                if grouppquota.SoftLimit is not None :
491                    if grouppquota.SoftLimit[0].upper() == "NONE" :
492                        grouppquota.SoftLimit = None
493                    else :   
494                        grouppquota.SoftLimit = int(grouppquota.SoftLimit[0])
495                grouppquota.HardLimit = fields.get("pykotaHardLimit")
496                if grouppquota.HardLimit is not None :
497                    if grouppquota.HardLimit[0].upper() == "NONE" :
498                        grouppquota.HardLimit = None
499                    else :   
500                        grouppquota.HardLimit = int(grouppquota.HardLimit[0])
501                grouppquota.DateLimit = fields.get("pykotaDateLimit")
502                if grouppquota.DateLimit is not None :
503                    if grouppquota.DateLimit[0].upper() == "NONE" : 
504                        grouppquota.DateLimit = None
505                    else :   
506                        grouppquota.DateLimit = grouppquota.DateLimit[0]
507                grouppquota.PageCounter = 0
508                grouppquota.LifePageCounter = 0
509                usernamesfilter = "".join(["(pykotaUserName=%s)" % member.Name for member in self.getGroupMembers(group)])
510                if usernamesfilter :
511                    usernamesfilter = "(|%s)" % usernamesfilter
512                if self.info["userquotabase"].lower() == "user" :
513                    base = self.info["userbase"]
514                else :
515                    base = self.info["userquotabase"]
516                result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)%s)" % (printer.Name, usernamesfilter), ["pykotaPageCounter", "pykotaLifePageCounter"], base=base)
517                if result :
518                    for userpquota in result :   
519                        grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0)
520                        grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0)
521                grouppquota.Exists = 1
522        return grouppquota
523       
524    def getPrinterLastJobFromBackend(self, printer) :       
525        """Extracts a printer's last job information."""
526        lastjob = StorageLastJob(self, printer)
527        result = self.doSearch("(&(objectClass=pykotaLastjob)(|(pykotaPrinterName=%s)(%s=%s)))" % (printer.Name, self.info["printerrdn"], printer.Name), ["pykotaLastJobIdent"], base=self.info["lastjobbase"])
528        if result :
529            lastjob.lastjobident = result[0][0]
530            lastjobident = result[0][1]["pykotaLastJobIdent"][0]
531            result = None
532            try :
533                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes", 
534                                                                  "pykotaHostName", 
535                                                                  "pykotaUserName", 
536                                                                  "pykotaPrinterName", 
537                                                                  "pykotaJobId", 
538                                                                  "pykotaPrinterPageCounter", 
539                                                                  "pykotaJobSize", 
540                                                                  "pykotaAction", 
541                                                                  "pykotaJobPrice", 
542                                                                  "pykotaFileName", 
543                                                                  "pykotaTitle", 
544                                                                  "pykotaCopies", 
545                                                                  "pykotaOptions", 
546                                                                  "pykotaBillingCode", 
547                                                                  "pykotaPages", 
548                                                                  "pykotaMD5Sum", 
549                                                                  "pykotaPrecomputedJobSize",
550                                                                  "pykotaPrecomputedJobPrice",
551                                                                  "createTimestamp" ], 
552                                                                base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
553            except PyKotaStorageError :   
554                pass # Last job entry exists, but job probably doesn't exist anymore.
555            if result :
556                fields = result[0][1]
557                lastjob.ident = result[0][0]
558                lastjob.JobId = fields.get("pykotaJobId")[0]
559                lastjob.UserName = fields.get("pykotaUserName")[0]
560                lastjob.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0])
561                try :
562                    lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0])
563                except ValueError :   
564                    lastjob.JobSize = None
565                try :   
566                    lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
567                except ValueError :   
568                    lastjob.JobPrice = None
569                lastjob.JobAction = fields.get("pykotaAction", [""])[0]
570                lastjob.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
571                lastjob.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
572                lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0])
573                lastjob.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
574                lastjob.JobHostName = fields.get("pykotaHostName", [""])[0]
575                lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
576                lastjob.JobBillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [None])[0])
577                lastjob.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
578                lastjob.JobPages = fields.get("pykotaPages", [""])[0]
579                try :
580                    lastjob.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
581                except ValueError :   
582                    lastjob.PrecomputedJobSize = None
583                try :   
584                    lastjob.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
585                except ValueError :   
586                    lastjob.PrecomputedJobPrice = None
587                if lastjob.JobTitle == lastjob.JobFileName == lastjob.JobOptions == "hidden" :
588                    (lastjob.JobTitle, lastjob.JobFileName, lastjob.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
589                date = fields.get("createTimestamp", ["19700101000000"])[0]
590                year = int(date[:4])
591                month = int(date[4:6])
592                day = int(date[6:8])
593                hour = int(date[8:10])
594                minute = int(date[10:12])
595                second = int(date[12:14])
596                lastjob.JobDate = "%04i-%02i-%02i %02i:%02i:%02i" % (year, month, day, hour, minute, second)
597                lastjob.Exists = 1
598        return lastjob
599       
600    def getGroupMembersFromBackend(self, group) :       
601        """Returns the group's members list."""
602        groupmembers = []
603        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (group.Name, self.info["grouprdn"], group.Name), [self.info["groupmembers"]], base=self.info["groupbase"])
604        if result :
605            for username in result[0][1].get(self.info["groupmembers"], []) :
606                groupmembers.append(self.getUser(username))
607        return groupmembers       
608       
609    def getUserGroupsFromBackend(self, user) :       
610        """Returns the user's groups list."""
611        groups = []
612        result = self.doSearch("(&(objectClass=pykotaGroup)(%s=%s))" % (self.info["groupmembers"], user.Name), [self.info["grouprdn"], "pykotaGroupName", "pykotaLimitBy"], base=self.info["groupbase"])
613        if result :
614            for (groupid, fields) in result :
615                groupname = (fields.get("pykotaGroupName", [None]) or fields.get(self.info["grouprdn"], [None]))[0]
616                group = self.getFromCache("GROUPS", groupname)
617                if group is None :
618                    group = StorageGroup(self, groupname)
619                    group.ident = groupid
620                    group.LimitBy = fields.get("pykotaLimitBy")
621                    if group.LimitBy is not None :
622                        group.LimitBy = group.LimitBy[0]
623                    else :   
624                        group.LimitBy = "quota"
625                    group.AccountBalance = 0.0
626                    group.LifeTimePaid = 0.0
627                    for member in self.getGroupMembers(group) :
628                        if member.Exists :
629                            group.AccountBalance += member.AccountBalance
630                            group.LifeTimePaid += member.LifeTimePaid
631                    group.Exists = 1
632                    self.cacheEntry("GROUPS", group.Name, group)
633                groups.append(group)
634        return groups       
635       
636    def getParentPrintersFromBackend(self, printer) :   
637        """Get all the printer groups this printer is a member of."""
638        pgroups = []
639        result = self.doSearch("(&(objectClass=pykotaPrinter)(uniqueMember=%s))" % printer.ident, ["pykotaPrinterName"], base=self.info["printerbase"])
640        if result :
641            for (printerid, fields) in result :
642                if printerid != printer.ident : # In case of integrity violation.
643                    parentprinter = self.getPrinter(fields.get("pykotaPrinterName")[0])
644                    if parentprinter.Exists :
645                        pgroups.append(parentprinter)
646        return pgroups
647       
648    def getMatchingPrinters(self, printerpattern) :
649        """Returns the list of all printers for which name matches a certain pattern."""
650        printers = []
651        # see comment at the same place in pgstorage.py
652        result = self.doSearch("(&(objectClass=pykotaPrinter)(|%s))" % "".join(["(pykotaPrinterName=%s)(%s=%s)" % (pname, self.info["printerrdn"], pname) for pname in printerpattern.split(",")]), ["pykotaPrinterName", "pykotaPricePerPage", "pykotaPricePerJob", "pykotaMaxJobSize", "pykotaPassThrough", "uniqueMember", "description"], base=self.info["printerbase"])
653        if result :
654            for (printerid, fields) in result :
655                printername = fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0]
656                printer = StoragePrinter(self, printername)
657                printer.ident = printerid
658                printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
659                printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
660                printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
661                printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
662                if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
663                    printer.PassThrough = 1
664                else :
665                    printer.PassThrough = 0
666                printer.uniqueMember = fields.get("uniqueMember", [])
667                printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
668                printer.Exists = 1
669                printers.append(printer)
670                self.cacheEntry("PRINTERS", printer.Name, printer)
671        return printers       
672       
673    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :       
674        """Returns the list of users who uses a given printer, along with their quotas."""
675        usersandquotas = []
676        if self.info["userquotabase"].lower() == "user" :
677           base = self.info["userbase"]
678        else :
679           base = self.info["userquotabase"]
680        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaUserName=%s)" % uname for uname in names])), ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], base=base)
681        if result :
682            for (userquotaid, fields) in result :
683                user = self.getUser(fields.get("pykotaUserName")[0])
684                userpquota = StorageUserPQuota(self, user, printer)
685                userpquota.ident = userquotaid
686                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
687                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
688                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
689                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
690                if userpquota.SoftLimit is not None :
691                    if userpquota.SoftLimit[0].upper() == "NONE" :
692                        userpquota.SoftLimit = None
693                    else :   
694                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
695                userpquota.HardLimit = fields.get("pykotaHardLimit")
696                if userpquota.HardLimit is not None :
697                    if userpquota.HardLimit[0].upper() == "NONE" :
698                        userpquota.HardLimit = None
699                    elif userpquota.HardLimit is not None :   
700                        userpquota.HardLimit = int(userpquota.HardLimit[0])
701                userpquota.DateLimit = fields.get("pykotaDateLimit")
702                if userpquota.DateLimit is not None :
703                    if userpquota.DateLimit[0].upper() == "NONE" : 
704                        userpquota.DateLimit = None
705                    else :   
706                        userpquota.DateLimit = userpquota.DateLimit[0]
707                userpquota.Exists = 1
708                usersandquotas.append((user, userpquota))
709                self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
710        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
711        return usersandquotas
712               
713    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :       
714        """Returns the list of groups which uses a given printer, along with their quotas."""
715        groupsandquotas = []
716        if self.info["groupquotabase"].lower() == "group" :
717           base = self.info["groupbase"]
718        else :
719           base = self.info["groupquotabase"]
720        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaGroupName=%s)" % gname for gname in names])), ["pykotaGroupName"], base=base)
721        if result :
722            for (groupquotaid, fields) in result :
723                group = self.getGroup(fields.get("pykotaGroupName")[0])
724                grouppquota = self.getGroupPQuota(group, printer)
725                groupsandquotas.append((group, grouppquota))
726        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
727        return groupsandquotas
728       
729    def addPrinter(self, printername) :       
730        """Adds a printer to the quota storage, returns it."""
731        fields = { self.info["printerrdn"] : printername,
732                   "objectClass" : ["pykotaObject", "pykotaPrinter"],
733                   "cn" : printername,
734                   "pykotaPrinterName" : printername,
735                   "pykotaPricePerPage" : "0.0",
736                   "pykotaPricePerJob" : "0.0",
737                   "pykotaMaxJobSize" : "0",
738                   "pykotaPassThrough" : "0",
739                 } 
740        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
741        self.doAdd(dn, fields)
742        return self.getPrinter(printername)
743       
744    def addUser(self, user) :       
745        """Adds a user to the quota storage, returns it."""
746        newfields = {
747                       "pykotaUserName" : user.Name,
748                       "pykotaLimitBy" : (user.LimitBy or "quota"),
749                       "pykotaOverCharge" : str(user.OverCharge),
750                    }   
751                       
752        if user.Email :
753            newfields.update({self.info["usermail"]: user.Email})
754        mustadd = 1
755        if self.info["newuser"].lower() != 'below' :
756            try :
757                (where, action) = [s.strip() for s in self.info["newuser"].split(",")]
758            except ValueError :
759                (where, action) = (self.info["newuser"].strip(), "fail")
760            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % (where, self.info["userrdn"], user.Name), None, base=self.info["userbase"])
761            if result :
762                (dn, fields) = result[0]
763                oc = fields.get("objectClass", fields.get("objectclass", []))
764                oc.extend(["pykotaAccount", "pykotaAccountBalance"])
765                fields.update(newfields)
766                fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0),
767                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })   
768                self.doModify(dn, fields)
769                mustadd = 0
770            else :
771                message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name)
772                if action.lower() == "warn" :   
773                    self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn")
774                else : # 'fail' or incorrect setting
775                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
776               
777        if mustadd :
778            if self.info["userbase"] == self.info["balancebase"] :           
779                fields = { self.info["userrdn"] : user.Name,
780                           "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
781                           "cn" : user.Name,
782                           "pykotaBalance" : str(user.AccountBalance or 0.0),
783                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
784                         } 
785            else :             
786                fields = { self.info["userrdn"] : user.Name,
787                           "objectClass" : ["pykotaObject", "pykotaAccount"],
788                           "cn" : user.Name,
789                         } 
790            fields.update(newfields)         
791            dn = "%s=%s,%s" % (self.info["userrdn"], user.Name, self.info["userbase"])
792            self.doAdd(dn, fields)
793            if self.info["userbase"] != self.info["balancebase"] :           
794                fields = { self.info["balancerdn"] : user.Name,
795                           "objectClass" : ["pykotaObject", "pykotaAccountBalance"],
796                           "cn" : user.Name,
797                           "pykotaBalance" : str(user.AccountBalance or 0.0),
798                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
799                         } 
800                dn = "%s=%s,%s" % (self.info["balancerdn"], user.Name, self.info["balancebase"])
801                self.doAdd(dn, fields)
802           
803        return self.getUser(user.Name)
804       
805    def addGroup(self, group) :       
806        """Adds a group to the quota storage, returns it."""
807        newfields = { 
808                      "pykotaGroupName" : group.Name,
809                      "pykotaLimitBy" : (group.LimitBy or "quota"),
810                    } 
811        mustadd = 1
812        if self.info["newgroup"].lower() != 'below' :
813            try :
814                (where, action) = [s.strip() for s in self.info["newgroup"].split(",")]
815            except ValueError :
816                (where, action) = (self.info["newgroup"].strip(), "fail")
817            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % (where, self.info["grouprdn"], group.Name), None, base=self.info["groupbase"])
818            if result :
819                (dn, fields) = result[0]
820                oc = fields.get("objectClass", fields.get("objectclass", []))
821                oc.extend(["pykotaGroup"])
822                fields.update(newfields)
823                self.doModify(dn, fields)
824                mustadd = 0
825            else :
826                message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name
827                if action.lower() == "warn" :   
828                    self.tool.printInfo("%s. A new entry will be created instead." % message, "warn")
829                else : # 'fail' or incorrect setting
830                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
831               
832        if mustadd :
833            fields = { self.info["grouprdn"] : group.Name,
834                       "objectClass" : ["pykotaObject", "pykotaGroup"],
835                       "cn" : group.Name,
836                     } 
837            fields.update(newfields)         
838            dn = "%s=%s,%s" % (self.info["grouprdn"], group.Name, self.info["groupbase"])
839            self.doAdd(dn, fields)
840        return self.getGroup(group.Name)
841       
842    def addUserToGroup(self, user, group) :   
843        """Adds an user to a group."""
844        if user.Name not in [u.Name for u in self.getGroupMembers(group)] :
845            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
846            if result :
847                fields = result[0][1]
848                if not fields.has_key(self.info["groupmembers"]) :
849                    fields[self.info["groupmembers"]] = []
850                fields[self.info["groupmembers"]].append(user.Name)
851                self.doModify(group.ident, fields)
852                group.Members.append(user)
853               
854    def addUserPQuota(self, user, printer) :
855        """Initializes a user print quota on a printer."""
856        uuid = self.genUUID()
857        fields = { "cn" : uuid,
858                   "objectClass" : ["pykotaObject", "pykotaUserPQuota"],
859                   "pykotaUserName" : user.Name,
860                   "pykotaPrinterName" : printer.Name,
861                   "pykotaDateLimit" : "None",
862                   "pykotaPageCounter" : "0",
863                   "pykotaLifePageCounter" : "0",
864                   "pykotaWarnCount" : "0",
865                 } 
866        if self.info["userquotabase"].lower() == "user" :
867            dn = "cn=%s,%s" % (uuid, user.ident)
868        else :   
869            dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
870        self.doAdd(dn, fields)
871        return self.getUserPQuota(user, printer)
872       
873    def addGroupPQuota(self, group, printer) :
874        """Initializes a group print quota on a printer."""
875        uuid = self.genUUID()
876        fields = { "cn" : uuid,
877                   "objectClass" : ["pykotaObject", "pykotaGroupPQuota"],
878                   "pykotaGroupName" : group.Name,
879                   "pykotaPrinterName" : printer.Name,
880                   "pykotaDateLimit" : "None",
881                 } 
882        if self.info["groupquotabase"].lower() == "group" :
883            dn = "cn=%s,%s" % (uuid, group.ident)
884        else :   
885            dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
886        self.doAdd(dn, fields)
887        return self.getGroupPQuota(group, printer)
888       
889    def writePrinterPrices(self, printer) :   
890        """Write the printer's prices back into the storage."""
891        fields = {
892                   "pykotaPricePerPage" : str(printer.PricePerPage),
893                   "pykotaPricePerJob" : str(printer.PricePerJob),
894                 }
895        self.doModify(printer.ident, fields)
896       
897    def writePrinterDescription(self, printer) :   
898        """Write the printer's description back into the storage."""
899        fields = {
900                   "description" : self.userCharsetToDatabase(printer.Description or ""),
901                 }
902        if fields["description"] :
903            self.doModify(printer.ident, fields)
904           
905    def setPrinterMaxJobSize(self, printer, maxjobsize) :     
906        """Write the printer's maxjobsize attribute."""
907        fields = {
908                   "pykotaMaxJobSize" : (maxjobsize and str(maxjobsize)) or "0",
909                 }
910        self.doModify(printer.ident, fields)
911       
912    def setPrinterPassThroughMode(self, printer, passthrough) :
913        """Write the printer's passthrough attribute."""
914        fields = {
915                   "pykotaPassThrough" : (passthrough and "t") or "f",
916                 }
917        self.doModify(printer.ident, fields)
918       
919    def writeUserOverCharge(self, user, factor) :
920        """Sets the user's overcharging coefficient."""
921        fields = {
922                   "pykotaOverCharge" : str(factor),
923                 }
924        self.doModify(user.ident, fields)
925       
926    def writeUserLimitBy(self, user, limitby) :   
927        """Sets the user's limiting factor."""
928        fields = {
929                   "pykotaLimitBy" : limitby,
930                 }
931        self.doModify(user.ident, fields)         
932       
933    def writeGroupLimitBy(self, group, limitby) :   
934        """Sets the group's limiting factor."""
935        fields = {
936                   "pykotaLimitBy" : limitby,
937                 }
938        self.doModify(group.ident, fields)         
939       
940    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :   
941        """Sets the date limit permanently for a user print quota."""
942        fields = {
943                   "pykotaDateLimit" : datelimit,
944                 }
945        return self.doModify(userpquota.ident, fields)
946           
947    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :   
948        """Sets the date limit permanently for a group print quota."""
949        fields = {
950                   "pykotaDateLimit" : datelimit,
951                 }
952        return self.doModify(grouppquota.ident, fields)
953       
954    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :   
955        """Increase page counters for a user print quota."""
956        fields = {
957                   "pykotaPageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
958                   "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
959                 }
960        return self.doModify(userpquota.ident, fields)         
961       
962    def writeUserPQuotaPagesCounters(self, userpquota, newpagecounter, newlifepagecounter) :   
963        """Sets the new page counters permanently for a user print quota."""
964        fields = {
965                   "pykotaPageCounter" : str(newpagecounter),
966                   "pykotaLifePageCounter" : str(newlifepagecounter),
967                   "pykotaDateLimit" : None,
968                   "pykotaWarnCount" : "0",
969                 } 
970        return self.doModify(userpquota.ident, fields)         
971       
972    def decreaseUserAccountBalance(self, user, amount) :   
973        """Decreases user's account balance from an amount."""
974        fields = {
975                   "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float },
976                 }
977        return self.doModify(user.idbalance, fields, flushcache=1)         
978       
979    def writeUserAccountBalance(self, user, newbalance, newlifetimepaid=None) :   
980        """Sets the new account balance and eventually new lifetime paid."""
981        fields = {
982                   "pykotaBalance" : str(newbalance),
983                 }
984        if newlifetimepaid is not None :
985            fields.update({ "pykotaLifeTimePaid" : str(newlifetimepaid) })
986        return self.doModify(user.idbalance, fields)         
987           
988    def writeNewPayment(self, user, amount, comment="") :
989        """Adds a new payment to the payments history."""
990        payments = []
991        for payment in user.Payments :
992            payments.append("%s # %s # %s" % (payment[0], str(payment[1]), base64.encodestring(self.userCharsetToDatabase(payment[2])).strip()))
993        payments.append("%s # %s # %s" % (str(DateTime.now()), str(amount), base64.encodestring(self.userCharsetToDatabase(comment)).strip()))
994        fields = {
995                   "pykotaPayments" : payments,
996                 }
997        return self.doModify(user.idbalance, fields)         
998       
999    def writeLastJobSize(self, lastjob, jobsize, jobprice) :       
1000        """Sets the last job's size permanently."""
1001        fields = {
1002                   "pykotaJobSize" : str(jobsize),
1003                   "pykotaJobPrice" : str(jobprice),
1004                 }
1005        self.doModify(lastjob.ident, fields)         
1006       
1007    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) :
1008        """Adds a job in a printer's history."""
1009        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1010            uuid = self.genUUID()
1011            dn = "cn=%s,%s" % (uuid, self.info["jobbase"])
1012        else :   
1013            uuid = printer.LastJob.ident[3:].split(",")[0]
1014            dn = printer.LastJob.ident
1015        if self.privacy :   
1016            # For legal reasons, we want to hide the title, filename and options
1017            title = filename = options = "hidden"
1018        fields = {
1019                   "objectClass" : ["pykotaObject", "pykotaJob"],
1020                   "cn" : uuid,
1021                   "pykotaUserName" : user.Name,
1022                   "pykotaPrinterName" : printer.Name,
1023                   "pykotaJobId" : jobid,
1024                   "pykotaPrinterPageCounter" : str(pagecounter),
1025                   "pykotaAction" : action,
1026                   "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename), 
1027                   "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title), 
1028                   "pykotaCopies" : str(copies), 
1029                   "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options), 
1030                   "pykotaHostName" : str(clienthost), 
1031                   "pykotaJobSizeBytes" : str(jobsizebytes),
1032                   "pykotaMD5Sum" : str(jobmd5sum),
1033                   "pykotaPages" : jobpages,            # don't add this attribute if it is not set, so no string conversion
1034                   "pykotaBillingCode" : self.userCharsetToDatabase(jobbilling), # don't add this attribute if it is not set, so no string conversion
1035                   "pykotaPrecomputedJobSize" : str(precomputedsize),
1036                   "pykotaPrecomputedJobPrice" : str(precomputedprice),
1037                 }
1038        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1039            if jobsize is not None :         
1040                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
1041            self.doAdd(dn, fields)
1042        else :   
1043            # here we explicitly want to reset jobsize to 'None' if needed
1044            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
1045            self.doModify(dn, fields)
1046           
1047        if printer.LastJob.Exists :
1048            fields = {
1049                       "pykotaLastJobIdent" : uuid,
1050                     }
1051            self.doModify(printer.LastJob.lastjobident, fields)         
1052        else :   
1053            lastjuuid = self.genUUID()
1054            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
1055            fields = {
1056                       "objectClass" : ["pykotaObject", "pykotaLastJob"],
1057                       "cn" : lastjuuid,
1058                       "pykotaPrinterName" : printer.Name,
1059                       "pykotaLastJobIdent" : uuid,
1060                     } 
1061            self.doAdd(lastjdn, fields)         
1062           
1063    def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
1064        """Sets soft and hard limits for a user quota."""
1065        fields = { 
1066                   "pykotaSoftLimit" : str(softlimit),
1067                   "pykotaHardLimit" : str(hardlimit),
1068                   "pykotaDateLimit" : "None",
1069                   "pykotaWarnCount" : "0",
1070                 }
1071        self.doModify(userpquota.ident, fields)
1072       
1073    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
1074        """Sets the warn counter value for a user quota."""
1075        fields = { 
1076                   "pykotaWarnCount" : str(warncount or 0),
1077                 }
1078        self.doModify(userpquota.ident, fields)
1079       
1080    def increaseUserPQuotaWarnCount(self, userpquota) :
1081        """Increases the warn counter value for a user quota."""
1082        fields = {
1083                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
1084                 }
1085        return self.doModify(userpquota.ident, fields)         
1086       
1087    def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
1088        """Sets soft and hard limits for a group quota on a specific printer."""
1089        fields = { 
1090                   "pykotaSoftLimit" : str(softlimit),
1091                   "pykotaHardLimit" : str(hardlimit),
1092                   "pykotaDateLimit" : "None",
1093                 }
1094        self.doModify(grouppquota.ident, fields)
1095           
1096    def writePrinterToGroup(self, pgroup, printer) :
1097        """Puts a printer into a printer group."""
1098        if printer.ident not in pgroup.uniqueMember :
1099            pgroup.uniqueMember.append(printer.ident)
1100            fields = {
1101                       "uniqueMember" : pgroup.uniqueMember
1102                     } 
1103            self.doModify(pgroup.ident, fields)         
1104           
1105    def removePrinterFromGroup(self, pgroup, printer) :
1106        """Removes a printer from a printer group."""
1107        try :
1108            pgroup.uniqueMember.remove(printer.ident)
1109        except ValueError :   
1110            pass
1111        else :   
1112            fields = {
1113                       "uniqueMember" : pgroup.uniqueMember,
1114                     } 
1115            self.doModify(pgroup.ident, fields)         
1116           
1117    def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, limit=100, start=None, end=None) :
1118        """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results."""
1119        precond = "(objectClass=pykotaJob)"
1120        where = []
1121        if user is not None :
1122            where.append("(pykotaUserName=%s)" % user.Name)
1123        if printer is not None :
1124            where.append("(pykotaPrinterName=%s)" % printer.Name)
1125        if hostname is not None :
1126            where.append("(pykotaHostName=%s)" % hostname)
1127        if billingcode is not None :
1128            where.append("(pykotaBillingCode=%s)" % self.userCharsetToDatabase(billingcode))
1129        if where :   
1130            where = "(&%s)" % "".join([precond] + where)
1131        else :   
1132            where = precond
1133        jobs = []   
1134        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
1135                                               "pykotaHostName", 
1136                                               "pykotaUserName", 
1137                                               "pykotaPrinterName", 
1138                                               "pykotaJobId", 
1139                                               "pykotaPrinterPageCounter", 
1140                                               "pykotaAction", 
1141                                               "pykotaJobSize", 
1142                                               "pykotaJobPrice", 
1143                                               "pykotaFileName", 
1144                                               "pykotaTitle", 
1145                                               "pykotaCopies", 
1146                                               "pykotaOptions", 
1147                                               "pykotaBillingCode", 
1148                                               "pykotaPages", 
1149                                               "pykotaMD5Sum", 
1150                                               "pykotaPrecomputedJobSize",
1151                                               "pykotaPrecomputedJobPrice",
1152                                               "createTimestamp" ], 
1153                                      base=self.info["jobbase"])
1154        if result :
1155            for (ident, fields) in result :
1156                job = StorageJob(self)
1157                job.ident = ident
1158                job.JobId = fields.get("pykotaJobId")[0]
1159                job.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
1160                try :
1161                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
1162                except ValueError :   
1163                    job.JobSize = None
1164                try :   
1165                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
1166                except ValueError :
1167                    job.JobPrice = None
1168                job.JobAction = fields.get("pykotaAction", [""])[0]
1169                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
1170                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
1171                job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
1172                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
1173                job.JobHostName = fields.get("pykotaHostName", [""])[0]
1174                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
1175                job.JobBillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [None])[0])
1176                job.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
1177                job.JobPages = fields.get("pykotaPages", [""])[0]
1178                try :
1179                    job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
1180                except ValueError :   
1181                    job.PrecomputedJobSize = None
1182                try :   
1183                    job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
1184                except ValueError :
1185                    job.PrecomputedJobPrice = None
1186                if job.JobTitle == job.JobFileName == job.JobOptions == "hidden" :
1187                    (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
1188                date = fields.get("createTimestamp", ["19700101000000"])[0]
1189                year = int(date[:4])
1190                month = int(date[4:6])
1191                day = int(date[6:8])
1192                hour = int(date[8:10])
1193                minute = int(date[10:12])
1194                second = int(date[12:14])
1195                job.JobDate = "%04i%02i%02i %02i:%02i:%02i" % (year, month, day, hour, minute, second)
1196                if ((start is None) and (end is None)) or \
1197                   ((start is None) and (job.JobDate <= end)) or \
1198                   ((end is None) and (job.JobDate >= start)) or \
1199                   ((job.JobDate >= start) and (job.JobDate <= end)) :
1200                    job.UserName = fields.get("pykotaUserName")[0]
1201                    job.PrinterName = fields.get("pykotaPrinterName")[0]
1202                    job.Exists = 1
1203                    jobs.append(job)
1204            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))       
1205            if limit :   
1206                jobs = jobs[:int(limit)]
1207        return jobs
1208       
1209    def deleteUser(self, user) :   
1210        """Completely deletes an user from the Quota Storage."""
1211        todelete = []   
1212        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % user.Name, base=self.info["jobbase"])
1213        for (ident, fields) in result :
1214            todelete.append(ident)
1215        if self.info["userquotabase"].lower() == "user" :
1216            base = self.info["userbase"]
1217        else :
1218            base = self.info["userquotabase"]
1219        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s))" % user.Name, ["pykotaPrinterName", "pykotaUserName"], base=base)
1220        for (ident, fields) in result :
1221            # ensure the user print quota entry will be deleted
1222            todelete.append(ident)
1223           
1224            # if last job of current printer was printed by the user
1225            # to delete, we also need to delete the printer's last job entry.
1226            printername = fields["pykotaPrinterName"][0]
1227            printer = self.getPrinter(printername)
1228            if printer.LastJob.UserName == user.Name :
1229                todelete.append(printer.LastJob.lastjobident)
1230           
1231        for ident in todelete :   
1232            self.doDelete(ident)
1233           
1234        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)   
1235        if result :
1236            fields = result[0][1]
1237            for k in fields.keys() :
1238                if k.startswith("pykota") :
1239                    del fields[k]
1240                elif k.lower() == "objectclass" :   
1241                    todelete = []
1242                    for i in range(len(fields[k])) :
1243                        if fields[k][i].startswith("pykota") : 
1244                            todelete.append(i)
1245                    todelete.sort()       
1246                    todelete.reverse()
1247                    for i in todelete :
1248                        del fields[k][i]
1249            if fields.get("objectClass") or fields.get("objectclass") :
1250                self.doModify(user.ident, fields, ignoreold=0)       
1251            else :   
1252                self.doDelete(user.ident)
1253        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % user.Name, ["pykotaUserName"], base=self.info["balancebase"])
1254        for (ident, fields) in result :
1255            self.doDelete(ident)
1256       
1257    def deleteGroup(self, group) :   
1258        """Completely deletes a group from the Quota Storage."""
1259        if self.info["groupquotabase"].lower() == "group" :
1260            base = self.info["groupbase"]
1261        else :
1262            base = self.info["groupquotabase"]
1263        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s))" % group.Name, ["pykotaGroupName"], base=base)
1264        for (ident, fields) in result :
1265            self.doDelete(ident)
1266        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
1267        if result :
1268            fields = result[0][1]
1269            for k in fields.keys() :
1270                if k.startswith("pykota") :
1271                    del fields[k]
1272                elif k.lower() == "objectclass" :   
1273                    todelete = []
1274                    for i in range(len(fields[k])) :
1275                        if fields[k][i].startswith("pykota") : 
1276                            todelete.append(i)
1277                    todelete.sort()       
1278                    todelete.reverse()
1279                    for i in todelete :
1280                        del fields[k][i]
1281            if fields.get("objectClass") or fields.get("objectclass") :
1282                self.doModify(group.ident, fields, ignoreold=0)       
1283            else :   
1284                self.doDelete(group.ident)
1285               
1286    def deletePrinter(self, printer) :   
1287        """Completely deletes a printer from the Quota Storage."""
1288        result = self.doSearch("(&(objectClass=pykotaLastJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["lastjobbase"])
1289        for (ident, fields) in result :
1290            self.doDelete(ident)
1291        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["jobbase"])
1292        for (ident, fields) in result :
1293            self.doDelete(ident)
1294        if self.info["groupquotabase"].lower() == "group" :
1295            base = self.info["groupbase"]
1296        else :
1297            base = self.info["groupquotabase"]
1298        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1299        for (ident, fields) in result :
1300            self.doDelete(ident)
1301        if self.info["userquotabase"].lower() == "user" :
1302            base = self.info["userbase"]
1303        else :
1304            base = self.info["userquotabase"]
1305        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1306        for (ident, fields) in result :
1307            self.doDelete(ident)
1308        for parent in self.getParentPrinters(printer) : 
1309            try :
1310                parent.uniqueMember.remove(printer.ident)
1311            except ValueError :   
1312                pass
1313            else :   
1314                fields = {
1315                           "uniqueMember" : parent.uniqueMember,
1316                         } 
1317                self.doModify(parent.ident, fields)         
1318        self.doDelete(printer.ident)   
1319       
1320    def deleteBillingCode(self, code) :
1321        """Deletes a billing code from the Quota Storage (no entries are deleted from the history)"""
1322        self.doDelete(code.ident)
1323       
1324    def extractPrinters(self, extractonly={}) :
1325        """Extracts all printer records."""
1326        pname = extractonly.get("printername")
1327        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1328        if entries :
1329            result = [ ("dn", "printername", "priceperpage", "priceperjob", "description", "maxjobsize", "passthrough") ]
1330            for entry in entries :
1331                if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") :
1332                    passthrough = "t"
1333                else :   
1334                    passthrough = "f"
1335                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description, entry.MaxJobSize, passthrough))
1336            return result 
1337       
1338    def extractUsers(self, extractonly={}) :
1339        """Extracts all user records."""
1340        uname = extractonly.get("username")
1341        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1342        if entries :
1343            result = [ ("dn", "username", "balance", "lifetimepaid", "limitby", "email") ]
1344            for entry in entries :
1345                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email))
1346            return result 
1347       
1348    def extractBillingcodes(self, extractonly={}) :
1349        """Extracts all billing codes records."""
1350        billingcode = extractonly.get("billingcode")
1351        entries = [b for b in [self.getBillingCode(label) for label in self.getAllBillingCodes(billingcode)] if b.Exists]
1352        if entries :
1353            result = [ ("dn", "billingcode", "balance", "pagecounter", "description") ]
1354            for entry in entries :
1355                result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description))
1356            return result 
1357       
1358    def extractGroups(self, extractonly={}) :
1359        """Extracts all group records."""
1360        gname = extractonly.get("groupname")
1361        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1362        if entries :
1363            result = [ ("dn", "groupname", "limitby", "balance", "lifetimepaid") ]
1364            for entry in entries :
1365                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid))
1366            return result 
1367       
1368    def extractPayments(self, extractonly={}) :
1369        """Extracts all payment records."""
1370        uname = extractonly.get("username")
1371        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1372        if entries :
1373            result = [ ("username", "amount", "date", "description") ]
1374            for entry in entries :
1375                for (date, amount, description) in entry.Payments :
1376                    result.append((entry.Name, amount, date, description))
1377            return result       
1378       
1379    def extractUpquotas(self, extractonly={}) :
1380        """Extracts all userpquota records."""
1381        pname = extractonly.get("printername")
1382        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1383        if entries :
1384            result = [ ("username", "printername", "dn", "userdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1385            uname = extractonly.get("username")
1386            for entry in entries :
1387                for (user, userpquota) in self.getPrinterUsersAndQuotas(entry, names=[uname or "*"]) :
1388                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit))
1389            return result
1390       
1391    def extractGpquotas(self, extractonly={}) :
1392        """Extracts all grouppquota records."""
1393        pname = extractonly.get("printername")
1394        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1395        if entries :
1396            result = [ ("groupname", "printername", "dn", "groupdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1397            gname = extractonly.get("groupname")
1398            for entry in entries :
1399                for (group, grouppquota) in self.getPrinterGroupsAndQuotas(entry, names=[gname or "*"]) :
1400                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
1401            return result
1402       
1403    def extractUmembers(self, extractonly={}) :
1404        """Extracts all user groups members."""
1405        gname = extractonly.get("groupname")
1406        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1407        if entries :
1408            result = [ ("groupname", "username", "groupdn", "userdn") ]
1409            uname = extractonly.get("username")
1410            for entry in entries :
1411                for member in entry.Members :
1412                    if (uname is None) or (member.Name == uname) :
1413                        result.append((entry.Name, member.Name, entry.ident, member.ident))
1414            return result       
1415               
1416    def extractPmembers(self, extractonly={}) :
1417        """Extracts all printer groups members."""
1418        pname = extractonly.get("printername")
1419        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1420        if entries :
1421            result = [ ("pgroupname", "printername", "pgroupdn", "printerdn") ]
1422            pgname = extractonly.get("pgroupname")
1423            for entry in entries :
1424                for parent in self.getParentPrinters(entry) :
1425                    if (pgname is None) or (parent.Name == pgname) :
1426                        result.append((parent.Name, entry.Name, parent.ident, entry.ident))
1427            return result       
1428       
1429    def extractHistory(self, extractonly={}) :
1430        """Extracts all jobhistory records."""
1431        uname = extractonly.get("username")
1432        if uname :
1433            user = self.getUser(uname)
1434        else :   
1435            user = None
1436        pname = extractonly.get("printername")
1437        if pname :
1438            printer = self.getPrinter(pname)
1439        else :   
1440            printer = None
1441        startdate = extractonly.get("start")
1442        enddate = extractonly.get("end")
1443        (startdate, enddate) = self.cleanDates(startdate, enddate)
1444        entries = self.retrieveHistory(user, printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), limit=None, start=startdate, end=enddate)
1445        if entries :
1446            result = [ ("username", "printername", "dn", "jobid", "pagecounter", "jobsize", "action", "jobdate", "filename", "title", "copies", "options", "jobprice", "hostname", "jobsizebytes", "md5sum", "pages", "billingcode", "precomputedjobsize", "precomputedjobprice") ] 
1447            for entry in entries :
1448                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)) 
1449            return result
1450           
1451    def getBillingCodeFromBackend(self, label) :
1452        """Extracts billing code information given its label : returns first matching billing code."""
1453        code = StorageBillingCode(self, label)
1454        ulabel = self.userCharsetToDatabase(label)
1455        result = self.doSearch("(&(objectClass=pykotaBilling)(pykotaBillingCode=%s))" % ulabel, ["pykotaBillingCode", "pykotaBalance", "pykotaPageCounter", "description"], base=self.info["billingcodebase"])
1456        if result :
1457            fields = result[0][1]       # take only first matching code, ignore the rest
1458            code.ident = result[0][0]
1459            code.BillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [ulabel])[0])
1460            code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1461            code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
1462            code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
1463            code.Exists = 1
1464        return code   
1465       
1466    def addBillingCode(self, label) :
1467        """Adds a billing code to the quota storage, returns it."""
1468        uuid = self.genUUID()
1469        dn = "cn=%s,%s" % (uuid, self.info["billingcodebase"])
1470        fields = { "objectClass" : ["pykotaObject", "pykotaBilling"],
1471                   "cn" : uuid,
1472                   "pykotaBillingCode" : self.userCharsetToDatabase(label),
1473                   "pykotaPageCounter" : "0",
1474                   "pykotaBalance" : "0.0",
1475                 } 
1476        self.doAdd(dn, fields)
1477        return self.getBillingCode(label)
1478       
1479    def writeBillingCodeDescription(self, code) :
1480        """Sets the new description for a billing code."""
1481        fields = {
1482                   "description" : self.userCharsetToDatabase(code.Description or ""), 
1483                 }
1484        if fields["description"] :
1485            self.doModify(code.ident, fields)
1486           
1487    def getMatchingBillingCodes(self, billingcodepattern) :
1488        """Returns the list of all billing codes which match a certain pattern."""
1489        codes = []
1490        result = self.doSearch("(&(objectClass=pykotaBilling)(|%s))" % "".join(["(pykotaBillingCode=%s)" % self.userCharsetToDatabase(bcode) for bcode in billingcodepattern.split(",")]), \
1491                                ["pykotaBillingCode", "description", "pykotaPageCounter", "pykotaBalance"], \
1492                                base=self.info["billingcodebase"])
1493        if result :
1494            for (codeid, fields) in result :
1495                codename = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0])
1496                code = StorageBillingCode(self, codename)
1497                code.ident = codeid
1498                code.BillingCode = codename
1499                code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1500                code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
1501                code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
1502                code.Exists = 1
1503                codes.append(code)
1504                self.cacheEntry("BILLINGCODES", code.BillingCode, code)
1505        return codes       
1506       
1507    def setBillingCodeValues(self, code, newpagecounter, newbalance) :
1508        """Sets the new page counter and balance for a billing code."""
1509        fields = {
1510                   "pykotaPageCounter" : str(newpagecounter),
1511                   "pykotaBalance" : str(newbalance),
1512                 } 
1513        return self.doModify(code.ident, fields)         
1514       
1515    def consumeBillingCode(self, code, pagecounter, balance) :
1516        """Consumes from a billing code."""
1517        fields = {
1518                   "pykotaBalance" : { "operator" : "-", "value" : balance, "convert" : float },
1519                   "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int },
1520                 }
1521        return self.doModify(code.ident, fields)         
Note: See TracBrowser for help on using the browser.