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

Revision 2622, 79.8 kB (checked in by jerome, 18 years ago)

Added 2006 to the copyright's years.

  • 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, 2005, 2006 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", ["19700101000000Z"])[0] # It's in UTC !
590                mxtime = DateTime.strptime(date[:14], "%Y%m%d%H%M%S").localtime()
591                lastjob.JobDate = mxtime.strftime("%Y%m%d %H:%M:%S")
592                lastjob.Exists = 1
593        return lastjob
594       
595    def getGroupMembersFromBackend(self, group) :       
596        """Returns the group's members list."""
597        groupmembers = []
598        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (group.Name, self.info["grouprdn"], group.Name), [self.info["groupmembers"]], base=self.info["groupbase"])
599        if result :
600            for username in result[0][1].get(self.info["groupmembers"], []) :
601                groupmembers.append(self.getUser(username))
602        return groupmembers       
603       
604    def getUserGroupsFromBackend(self, user) :       
605        """Returns the user's groups list."""
606        groups = []
607        result = self.doSearch("(&(objectClass=pykotaGroup)(%s=%s))" % (self.info["groupmembers"], user.Name), [self.info["grouprdn"], "pykotaGroupName", "pykotaLimitBy"], base=self.info["groupbase"])
608        if result :
609            for (groupid, fields) in result :
610                groupname = (fields.get("pykotaGroupName", [None]) or fields.get(self.info["grouprdn"], [None]))[0]
611                group = self.getFromCache("GROUPS", groupname)
612                if group is None :
613                    group = StorageGroup(self, groupname)
614                    group.ident = groupid
615                    group.LimitBy = fields.get("pykotaLimitBy")
616                    if group.LimitBy is not None :
617                        group.LimitBy = group.LimitBy[0]
618                    else :   
619                        group.LimitBy = "quota"
620                    group.AccountBalance = 0.0
621                    group.LifeTimePaid = 0.0
622                    for member in self.getGroupMembers(group) :
623                        if member.Exists :
624                            group.AccountBalance += member.AccountBalance
625                            group.LifeTimePaid += member.LifeTimePaid
626                    group.Exists = 1
627                    self.cacheEntry("GROUPS", group.Name, group)
628                groups.append(group)
629        return groups       
630       
631    def getParentPrintersFromBackend(self, printer) :   
632        """Get all the printer groups this printer is a member of."""
633        pgroups = []
634        result = self.doSearch("(&(objectClass=pykotaPrinter)(uniqueMember=%s))" % printer.ident, ["pykotaPrinterName"], base=self.info["printerbase"])
635        if result :
636            for (printerid, fields) in result :
637                if printerid != printer.ident : # In case of integrity violation.
638                    parentprinter = self.getPrinter(fields.get("pykotaPrinterName")[0])
639                    if parentprinter.Exists :
640                        pgroups.append(parentprinter)
641        return pgroups
642       
643    def getMatchingPrinters(self, printerpattern) :
644        """Returns the list of all printers for which name matches a certain pattern."""
645        printers = []
646        # see comment at the same place in pgstorage.py
647        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"])
648        if result :
649            for (printerid, fields) in result :
650                printername = fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0]
651                printer = StoragePrinter(self, printername)
652                printer.ident = printerid
653                printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
654                printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
655                printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
656                printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
657                if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
658                    printer.PassThrough = 1
659                else :
660                    printer.PassThrough = 0
661                printer.uniqueMember = fields.get("uniqueMember", [])
662                printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
663                printer.Exists = 1
664                printers.append(printer)
665                self.cacheEntry("PRINTERS", printer.Name, printer)
666        return printers       
667       
668    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :       
669        """Returns the list of users who uses a given printer, along with their quotas."""
670        usersandquotas = []
671        if self.info["userquotabase"].lower() == "user" :
672           base = self.info["userbase"]
673        else :
674           base = self.info["userquotabase"]
675        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)
676        if result :
677            for (userquotaid, fields) in result :
678                user = self.getUser(fields.get("pykotaUserName")[0])
679                userpquota = StorageUserPQuota(self, user, printer)
680                userpquota.ident = userquotaid
681                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
682                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
683                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
684                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
685                if userpquota.SoftLimit is not None :
686                    if userpquota.SoftLimit[0].upper() == "NONE" :
687                        userpquota.SoftLimit = None
688                    else :   
689                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
690                userpquota.HardLimit = fields.get("pykotaHardLimit")
691                if userpquota.HardLimit is not None :
692                    if userpquota.HardLimit[0].upper() == "NONE" :
693                        userpquota.HardLimit = None
694                    elif userpquota.HardLimit is not None :   
695                        userpquota.HardLimit = int(userpquota.HardLimit[0])
696                userpquota.DateLimit = fields.get("pykotaDateLimit")
697                if userpquota.DateLimit is not None :
698                    if userpquota.DateLimit[0].upper() == "NONE" : 
699                        userpquota.DateLimit = None
700                    else :   
701                        userpquota.DateLimit = userpquota.DateLimit[0]
702                userpquota.Exists = 1
703                usersandquotas.append((user, userpquota))
704                self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
705        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
706        return usersandquotas
707               
708    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :       
709        """Returns the list of groups which uses a given printer, along with their quotas."""
710        groupsandquotas = []
711        if self.info["groupquotabase"].lower() == "group" :
712           base = self.info["groupbase"]
713        else :
714           base = self.info["groupquotabase"]
715        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaGroupName=%s)" % gname for gname in names])), ["pykotaGroupName"], base=base)
716        if result :
717            for (groupquotaid, fields) in result :
718                group = self.getGroup(fields.get("pykotaGroupName")[0])
719                grouppquota = self.getGroupPQuota(group, printer)
720                groupsandquotas.append((group, grouppquota))
721        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
722        return groupsandquotas
723       
724    def addPrinter(self, printername) :       
725        """Adds a printer to the quota storage, returns it."""
726        fields = { self.info["printerrdn"] : printername,
727                   "objectClass" : ["pykotaObject", "pykotaPrinter"],
728                   "cn" : printername,
729                   "pykotaPrinterName" : printername,
730                   "pykotaPricePerPage" : "0.0",
731                   "pykotaPricePerJob" : "0.0",
732                   "pykotaMaxJobSize" : "0",
733                   "pykotaPassThrough" : "0",
734                 } 
735        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
736        self.doAdd(dn, fields)
737        return self.getPrinter(printername)
738       
739    def addUser(self, user) :       
740        """Adds a user to the quota storage, returns it."""
741        newfields = {
742                       "pykotaUserName" : user.Name,
743                       "pykotaLimitBy" : (user.LimitBy or "quota"),
744                       "pykotaOverCharge" : str(user.OverCharge),
745                    }   
746                       
747        if user.Email :
748            newfields.update({self.info["usermail"]: user.Email})
749        mustadd = 1
750        if self.info["newuser"].lower() != 'below' :
751            try :
752                (where, action) = [s.strip() for s in self.info["newuser"].split(",")]
753            except ValueError :
754                (where, action) = (self.info["newuser"].strip(), "fail")
755            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % (where, self.info["userrdn"], user.Name), None, base=self.info["userbase"])
756            if result :
757                (dn, fields) = result[0]
758                oc = fields.get("objectClass", fields.get("objectclass", []))
759                oc.extend(["pykotaAccount", "pykotaAccountBalance"])
760                fields.update(newfields)
761                fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0),
762                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })   
763                self.doModify(dn, fields)
764                mustadd = 0
765            else :
766                message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name)
767                if action.lower() == "warn" :   
768                    self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn")
769                else : # 'fail' or incorrect setting
770                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
771               
772        if mustadd :
773            if self.info["userbase"] == self.info["balancebase"] :           
774                fields = { self.info["userrdn"] : user.Name,
775                           "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
776                           "cn" : user.Name,
777                           "pykotaBalance" : str(user.AccountBalance or 0.0),
778                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
779                         } 
780            else :             
781                fields = { self.info["userrdn"] : user.Name,
782                           "objectClass" : ["pykotaObject", "pykotaAccount"],
783                           "cn" : user.Name,
784                         } 
785            fields.update(newfields)         
786            dn = "%s=%s,%s" % (self.info["userrdn"], user.Name, self.info["userbase"])
787            self.doAdd(dn, fields)
788            if self.info["userbase"] != self.info["balancebase"] :           
789                fields = { self.info["balancerdn"] : user.Name,
790                           "objectClass" : ["pykotaObject", "pykotaAccountBalance"],
791                           "cn" : user.Name,
792                           "pykotaBalance" : str(user.AccountBalance or 0.0),
793                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
794                         } 
795                dn = "%s=%s,%s" % (self.info["balancerdn"], user.Name, self.info["balancebase"])
796                self.doAdd(dn, fields)
797           
798        return self.getUser(user.Name)
799       
800    def addGroup(self, group) :       
801        """Adds a group to the quota storage, returns it."""
802        newfields = { 
803                      "pykotaGroupName" : group.Name,
804                      "pykotaLimitBy" : (group.LimitBy or "quota"),
805                    } 
806        mustadd = 1
807        if self.info["newgroup"].lower() != 'below' :
808            try :
809                (where, action) = [s.strip() for s in self.info["newgroup"].split(",")]
810            except ValueError :
811                (where, action) = (self.info["newgroup"].strip(), "fail")
812            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % (where, self.info["grouprdn"], group.Name), None, base=self.info["groupbase"])
813            if result :
814                (dn, fields) = result[0]
815                oc = fields.get("objectClass", fields.get("objectclass", []))
816                oc.extend(["pykotaGroup"])
817                fields.update(newfields)
818                self.doModify(dn, fields)
819                mustadd = 0
820            else :
821                message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name
822                if action.lower() == "warn" :   
823                    self.tool.printInfo("%s. A new entry will be created instead." % message, "warn")
824                else : # 'fail' or incorrect setting
825                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
826               
827        if mustadd :
828            fields = { self.info["grouprdn"] : group.Name,
829                       "objectClass" : ["pykotaObject", "pykotaGroup"],
830                       "cn" : group.Name,
831                     } 
832            fields.update(newfields)         
833            dn = "%s=%s,%s" % (self.info["grouprdn"], group.Name, self.info["groupbase"])
834            self.doAdd(dn, fields)
835        return self.getGroup(group.Name)
836       
837    def addUserToGroup(self, user, group) :   
838        """Adds an user to a group."""
839        if user.Name not in [u.Name for u in self.getGroupMembers(group)] :
840            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
841            if result :
842                fields = result[0][1]
843                if not fields.has_key(self.info["groupmembers"]) :
844                    fields[self.info["groupmembers"]] = []
845                fields[self.info["groupmembers"]].append(user.Name)
846                self.doModify(group.ident, fields)
847                group.Members.append(user)
848               
849    def addUserPQuota(self, user, printer) :
850        """Initializes a user print quota on a printer."""
851        uuid = self.genUUID()
852        fields = { "cn" : uuid,
853                   "objectClass" : ["pykotaObject", "pykotaUserPQuota"],
854                   "pykotaUserName" : user.Name,
855                   "pykotaPrinterName" : printer.Name,
856                   "pykotaDateLimit" : "None",
857                   "pykotaPageCounter" : "0",
858                   "pykotaLifePageCounter" : "0",
859                   "pykotaWarnCount" : "0",
860                 } 
861        if self.info["userquotabase"].lower() == "user" :
862            dn = "cn=%s,%s" % (uuid, user.ident)
863        else :   
864            dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
865        self.doAdd(dn, fields)
866        return self.getUserPQuota(user, printer)
867       
868    def addGroupPQuota(self, group, printer) :
869        """Initializes a group print quota on a printer."""
870        uuid = self.genUUID()
871        fields = { "cn" : uuid,
872                   "objectClass" : ["pykotaObject", "pykotaGroupPQuota"],
873                   "pykotaGroupName" : group.Name,
874                   "pykotaPrinterName" : printer.Name,
875                   "pykotaDateLimit" : "None",
876                 } 
877        if self.info["groupquotabase"].lower() == "group" :
878            dn = "cn=%s,%s" % (uuid, group.ident)
879        else :   
880            dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
881        self.doAdd(dn, fields)
882        return self.getGroupPQuota(group, printer)
883       
884    def writePrinterPrices(self, printer) :   
885        """Write the printer's prices back into the storage."""
886        fields = {
887                   "pykotaPricePerPage" : str(printer.PricePerPage),
888                   "pykotaPricePerJob" : str(printer.PricePerJob),
889                 }
890        self.doModify(printer.ident, fields)
891       
892    def writePrinterDescription(self, printer) :   
893        """Write the printer's description back into the storage."""
894        fields = {
895                   "description" : self.userCharsetToDatabase(printer.Description or ""),
896                 }
897        if fields["description"] :
898            self.doModify(printer.ident, fields)
899           
900    def setPrinterMaxJobSize(self, printer, maxjobsize) :     
901        """Write the printer's maxjobsize attribute."""
902        fields = {
903                   "pykotaMaxJobSize" : (maxjobsize and str(maxjobsize)) or "0",
904                 }
905        self.doModify(printer.ident, fields)
906       
907    def setPrinterPassThroughMode(self, printer, passthrough) :
908        """Write the printer's passthrough attribute."""
909        fields = {
910                   "pykotaPassThrough" : (passthrough and "t") or "f",
911                 }
912        self.doModify(printer.ident, fields)
913       
914    def writeUserOverCharge(self, user, factor) :
915        """Sets the user's overcharging coefficient."""
916        fields = {
917                   "pykotaOverCharge" : str(factor),
918                 }
919        self.doModify(user.ident, fields)
920       
921    def writeUserLimitBy(self, user, limitby) :   
922        """Sets the user's limiting factor."""
923        fields = {
924                   "pykotaLimitBy" : limitby,
925                 }
926        self.doModify(user.ident, fields)         
927       
928    def writeGroupLimitBy(self, group, limitby) :   
929        """Sets the group's limiting factor."""
930        fields = {
931                   "pykotaLimitBy" : limitby,
932                 }
933        self.doModify(group.ident, fields)         
934       
935    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :   
936        """Sets the date limit permanently for a user print quota."""
937        fields = {
938                   "pykotaDateLimit" : datelimit,
939                 }
940        return self.doModify(userpquota.ident, fields)
941           
942    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :   
943        """Sets the date limit permanently for a group print quota."""
944        fields = {
945                   "pykotaDateLimit" : datelimit,
946                 }
947        return self.doModify(grouppquota.ident, fields)
948       
949    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :   
950        """Increase page counters for a user print quota."""
951        fields = {
952                   "pykotaPageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
953                   "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
954                 }
955        return self.doModify(userpquota.ident, fields)         
956       
957    def writeUserPQuotaPagesCounters(self, userpquota, newpagecounter, newlifepagecounter) :   
958        """Sets the new page counters permanently for a user print quota."""
959        fields = {
960                   "pykotaPageCounter" : str(newpagecounter),
961                   "pykotaLifePageCounter" : str(newlifepagecounter),
962                   "pykotaDateLimit" : None,
963                   "pykotaWarnCount" : "0",
964                 } 
965        return self.doModify(userpquota.ident, fields)         
966       
967    def decreaseUserAccountBalance(self, user, amount) :   
968        """Decreases user's account balance from an amount."""
969        fields = {
970                   "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float },
971                 }
972        return self.doModify(user.idbalance, fields, flushcache=1)         
973       
974    def writeUserAccountBalance(self, user, newbalance, newlifetimepaid=None) :   
975        """Sets the new account balance and eventually new lifetime paid."""
976        fields = {
977                   "pykotaBalance" : str(newbalance),
978                 }
979        if newlifetimepaid is not None :
980            fields.update({ "pykotaLifeTimePaid" : str(newlifetimepaid) })
981        return self.doModify(user.idbalance, fields)         
982           
983    def writeNewPayment(self, user, amount, comment="") :
984        """Adds a new payment to the payments history."""
985        payments = []
986        for payment in user.Payments :
987            payments.append("%s # %s # %s" % (payment[0], str(payment[1]), base64.encodestring(self.userCharsetToDatabase(payment[2])).strip()))
988        payments.append("%s # %s # %s" % (str(DateTime.now()), str(amount), base64.encodestring(self.userCharsetToDatabase(comment)).strip()))
989        fields = {
990                   "pykotaPayments" : payments,
991                 }
992        return self.doModify(user.idbalance, fields)         
993       
994    def writeLastJobSize(self, lastjob, jobsize, jobprice) :       
995        """Sets the last job's size permanently."""
996        fields = {
997                   "pykotaJobSize" : str(jobsize),
998                   "pykotaJobPrice" : str(jobprice),
999                 }
1000        self.doModify(lastjob.ident, fields)         
1001       
1002    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) :
1003        """Adds a job in a printer's history."""
1004        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1005            uuid = self.genUUID()
1006            dn = "cn=%s,%s" % (uuid, self.info["jobbase"])
1007        else :   
1008            uuid = printer.LastJob.ident[3:].split(",")[0]
1009            dn = printer.LastJob.ident
1010        if self.privacy :   
1011            # For legal reasons, we want to hide the title, filename and options
1012            title = filename = options = "hidden"
1013        fields = {
1014                   "objectClass" : ["pykotaObject", "pykotaJob"],
1015                   "cn" : uuid,
1016                   "pykotaUserName" : user.Name,
1017                   "pykotaPrinterName" : printer.Name,
1018                   "pykotaJobId" : jobid,
1019                   "pykotaPrinterPageCounter" : str(pagecounter),
1020                   "pykotaAction" : action,
1021                   "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename), 
1022                   "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title), 
1023                   "pykotaCopies" : str(copies), 
1024                   "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options), 
1025                   "pykotaHostName" : str(clienthost), 
1026                   "pykotaJobSizeBytes" : str(jobsizebytes),
1027                   "pykotaMD5Sum" : str(jobmd5sum),
1028                   "pykotaPages" : jobpages,            # don't add this attribute if it is not set, so no string conversion
1029                   "pykotaBillingCode" : self.userCharsetToDatabase(jobbilling), # don't add this attribute if it is not set, so no string conversion
1030                   "pykotaPrecomputedJobSize" : str(precomputedsize),
1031                   "pykotaPrecomputedJobPrice" : str(precomputedprice),
1032                 }
1033        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1034            if jobsize is not None :         
1035                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
1036            self.doAdd(dn, fields)
1037        else :   
1038            # here we explicitly want to reset jobsize to 'None' if needed
1039            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
1040            self.doModify(dn, fields)
1041           
1042        if printer.LastJob.Exists :
1043            fields = {
1044                       "pykotaLastJobIdent" : uuid,
1045                     }
1046            self.doModify(printer.LastJob.lastjobident, fields)         
1047        else :   
1048            lastjuuid = self.genUUID()
1049            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
1050            fields = {
1051                       "objectClass" : ["pykotaObject", "pykotaLastJob"],
1052                       "cn" : lastjuuid,
1053                       "pykotaPrinterName" : printer.Name,
1054                       "pykotaLastJobIdent" : uuid,
1055                     } 
1056            self.doAdd(lastjdn, fields)         
1057           
1058    def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
1059        """Sets soft and hard limits for a user quota."""
1060        fields = { 
1061                   "pykotaSoftLimit" : str(softlimit),
1062                   "pykotaHardLimit" : str(hardlimit),
1063                   "pykotaDateLimit" : "None",
1064                   "pykotaWarnCount" : "0",
1065                 }
1066        self.doModify(userpquota.ident, fields)
1067       
1068    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
1069        """Sets the warn counter value for a user quota."""
1070        fields = { 
1071                   "pykotaWarnCount" : str(warncount or 0),
1072                 }
1073        self.doModify(userpquota.ident, fields)
1074       
1075    def increaseUserPQuotaWarnCount(self, userpquota) :
1076        """Increases the warn counter value for a user quota."""
1077        fields = {
1078                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
1079                 }
1080        return self.doModify(userpquota.ident, fields)         
1081       
1082    def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
1083        """Sets soft and hard limits for a group quota on a specific printer."""
1084        fields = { 
1085                   "pykotaSoftLimit" : str(softlimit),
1086                   "pykotaHardLimit" : str(hardlimit),
1087                   "pykotaDateLimit" : "None",
1088                 }
1089        self.doModify(grouppquota.ident, fields)
1090           
1091    def writePrinterToGroup(self, pgroup, printer) :
1092        """Puts a printer into a printer group."""
1093        if printer.ident not in pgroup.uniqueMember :
1094            pgroup.uniqueMember.append(printer.ident)
1095            fields = {
1096                       "uniqueMember" : pgroup.uniqueMember
1097                     } 
1098            self.doModify(pgroup.ident, fields)         
1099           
1100    def removePrinterFromGroup(self, pgroup, printer) :
1101        """Removes a printer from a printer group."""
1102        try :
1103            pgroup.uniqueMember.remove(printer.ident)
1104        except ValueError :   
1105            pass
1106        else :   
1107            fields = {
1108                       "uniqueMember" : pgroup.uniqueMember,
1109                     } 
1110            self.doModify(pgroup.ident, fields)         
1111           
1112    def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, limit=100, start=None, end=None) :
1113        """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results."""
1114        precond = "(objectClass=pykotaJob)"
1115        where = []
1116        if user is not None :
1117            where.append("(pykotaUserName=%s)" % user.Name)
1118        if printer is not None :
1119            where.append("(pykotaPrinterName=%s)" % printer.Name)
1120        if hostname is not None :
1121            where.append("(pykotaHostName=%s)" % hostname)
1122        if billingcode is not None :
1123            where.append("(pykotaBillingCode=%s)" % self.userCharsetToDatabase(billingcode))
1124        if where :   
1125            where = "(&%s)" % "".join([precond] + where)
1126        else :   
1127            where = precond
1128        jobs = []   
1129        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
1130                                               "pykotaHostName", 
1131                                               "pykotaUserName", 
1132                                               "pykotaPrinterName", 
1133                                               "pykotaJobId", 
1134                                               "pykotaPrinterPageCounter", 
1135                                               "pykotaAction", 
1136                                               "pykotaJobSize", 
1137                                               "pykotaJobPrice", 
1138                                               "pykotaFileName", 
1139                                               "pykotaTitle", 
1140                                               "pykotaCopies", 
1141                                               "pykotaOptions", 
1142                                               "pykotaBillingCode", 
1143                                               "pykotaPages", 
1144                                               "pykotaMD5Sum", 
1145                                               "pykotaPrecomputedJobSize",
1146                                               "pykotaPrecomputedJobPrice",
1147                                               "createTimestamp" ], 
1148                                      base=self.info["jobbase"])
1149        if result :
1150            for (ident, fields) in result :
1151                job = StorageJob(self)
1152                job.ident = ident
1153                job.JobId = fields.get("pykotaJobId")[0]
1154                job.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
1155                try :
1156                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
1157                except ValueError :   
1158                    job.JobSize = None
1159                try :   
1160                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
1161                except ValueError :
1162                    job.JobPrice = None
1163                job.JobAction = fields.get("pykotaAction", [""])[0]
1164                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
1165                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
1166                job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
1167                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
1168                job.JobHostName = fields.get("pykotaHostName", [""])[0]
1169                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
1170                job.JobBillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [None])[0])
1171                job.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
1172                job.JobPages = fields.get("pykotaPages", [""])[0]
1173                try :
1174                    job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
1175                except ValueError :   
1176                    job.PrecomputedJobSize = None
1177                try :   
1178                    job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
1179                except ValueError :
1180                    job.PrecomputedJobPrice = None
1181                if job.JobTitle == job.JobFileName == job.JobOptions == "hidden" :
1182                    (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
1183                date = fields.get("createTimestamp", ["19700101000000Z"])[0] # It's in UTC !
1184                mxtime = DateTime.strptime(date[:14], "%Y%m%d%H%M%S").localtime()
1185                job.JobDate = mxtime.strftime("%Y%m%d %H:%M:%S")
1186                if ((start is None) and (end is None)) or \
1187                   ((start is None) and (job.JobDate <= end)) or \
1188                   ((end is None) and (job.JobDate >= start)) or \
1189                   ((job.JobDate >= start) and (job.JobDate <= end)) :
1190                    job.UserName = fields.get("pykotaUserName")[0]
1191                    job.PrinterName = fields.get("pykotaPrinterName")[0]
1192                    job.Exists = 1
1193                    jobs.append(job)
1194            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))       
1195            if limit :   
1196                jobs = jobs[:int(limit)]
1197        return jobs
1198       
1199    def deleteUser(self, user) :   
1200        """Completely deletes an user from the Quota Storage."""
1201        todelete = []   
1202        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % user.Name, base=self.info["jobbase"])
1203        for (ident, fields) in result :
1204            todelete.append(ident)
1205        if self.info["userquotabase"].lower() == "user" :
1206            base = self.info["userbase"]
1207        else :
1208            base = self.info["userquotabase"]
1209        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s))" % user.Name, ["pykotaPrinterName", "pykotaUserName"], base=base)
1210        for (ident, fields) in result :
1211            # ensure the user print quota entry will be deleted
1212            todelete.append(ident)
1213           
1214            # if last job of current printer was printed by the user
1215            # to delete, we also need to delete the printer's last job entry.
1216            printername = fields["pykotaPrinterName"][0]
1217            printer = self.getPrinter(printername)
1218            if printer.LastJob.UserName == user.Name :
1219                todelete.append(printer.LastJob.lastjobident)
1220           
1221        for ident in todelete :   
1222            self.doDelete(ident)
1223           
1224        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)   
1225        if result :
1226            fields = result[0][1]
1227            for k in fields.keys() :
1228                if k.startswith("pykota") :
1229                    del fields[k]
1230                elif k.lower() == "objectclass" :   
1231                    todelete = []
1232                    for i in range(len(fields[k])) :
1233                        if fields[k][i].startswith("pykota") : 
1234                            todelete.append(i)
1235                    todelete.sort()       
1236                    todelete.reverse()
1237                    for i in todelete :
1238                        del fields[k][i]
1239            if fields.get("objectClass") or fields.get("objectclass") :
1240                self.doModify(user.ident, fields, ignoreold=0)       
1241            else :   
1242                self.doDelete(user.ident)
1243        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % user.Name, ["pykotaUserName"], base=self.info["balancebase"])
1244        for (ident, fields) in result :
1245            self.doDelete(ident)
1246       
1247    def deleteGroup(self, group) :   
1248        """Completely deletes a group from the Quota Storage."""
1249        if self.info["groupquotabase"].lower() == "group" :
1250            base = self.info["groupbase"]
1251        else :
1252            base = self.info["groupquotabase"]
1253        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s))" % group.Name, ["pykotaGroupName"], base=base)
1254        for (ident, fields) in result :
1255            self.doDelete(ident)
1256        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
1257        if result :
1258            fields = result[0][1]
1259            for k in fields.keys() :
1260                if k.startswith("pykota") :
1261                    del fields[k]
1262                elif k.lower() == "objectclass" :   
1263                    todelete = []
1264                    for i in range(len(fields[k])) :
1265                        if fields[k][i].startswith("pykota") : 
1266                            todelete.append(i)
1267                    todelete.sort()       
1268                    todelete.reverse()
1269                    for i in todelete :
1270                        del fields[k][i]
1271            if fields.get("objectClass") or fields.get("objectclass") :
1272                self.doModify(group.ident, fields, ignoreold=0)       
1273            else :   
1274                self.doDelete(group.ident)
1275               
1276    def deletePrinter(self, printer) :   
1277        """Completely deletes a printer from the Quota Storage."""
1278        result = self.doSearch("(&(objectClass=pykotaLastJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["lastjobbase"])
1279        for (ident, fields) in result :
1280            self.doDelete(ident)
1281        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["jobbase"])
1282        for (ident, fields) in result :
1283            self.doDelete(ident)
1284        if self.info["groupquotabase"].lower() == "group" :
1285            base = self.info["groupbase"]
1286        else :
1287            base = self.info["groupquotabase"]
1288        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1289        for (ident, fields) in result :
1290            self.doDelete(ident)
1291        if self.info["userquotabase"].lower() == "user" :
1292            base = self.info["userbase"]
1293        else :
1294            base = self.info["userquotabase"]
1295        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1296        for (ident, fields) in result :
1297            self.doDelete(ident)
1298        for parent in self.getParentPrinters(printer) : 
1299            try :
1300                parent.uniqueMember.remove(printer.ident)
1301            except ValueError :   
1302                pass
1303            else :   
1304                fields = {
1305                           "uniqueMember" : parent.uniqueMember,
1306                         } 
1307                self.doModify(parent.ident, fields)         
1308        self.doDelete(printer.ident)   
1309       
1310    def deleteBillingCode(self, code) :
1311        """Deletes a billing code from the Quota Storage (no entries are deleted from the history)"""
1312        self.doDelete(code.ident)
1313       
1314    def extractPrinters(self, extractonly={}) :
1315        """Extracts all printer records."""
1316        pname = extractonly.get("printername")
1317        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1318        if entries :
1319            result = [ ("dn", "printername", "priceperpage", "priceperjob", "description", "maxjobsize", "passthrough") ]
1320            for entry in entries :
1321                if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") :
1322                    passthrough = "t"
1323                else :   
1324                    passthrough = "f"
1325                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description, entry.MaxJobSize, passthrough))
1326            return result 
1327       
1328    def extractUsers(self, extractonly={}) :
1329        """Extracts all user records."""
1330        uname = extractonly.get("username")
1331        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1332        if entries :
1333            result = [ ("dn", "username", "balance", "lifetimepaid", "limitby", "email") ]
1334            for entry in entries :
1335                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email))
1336            return result 
1337       
1338    def extractBillingcodes(self, extractonly={}) :
1339        """Extracts all billing codes records."""
1340        billingcode = extractonly.get("billingcode")
1341        entries = [b for b in [self.getBillingCode(label) for label in self.getAllBillingCodes(billingcode)] if b.Exists]
1342        if entries :
1343            result = [ ("dn", "billingcode", "balance", "pagecounter", "description") ]
1344            for entry in entries :
1345                result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description))
1346            return result 
1347       
1348    def extractGroups(self, extractonly={}) :
1349        """Extracts all group records."""
1350        gname = extractonly.get("groupname")
1351        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1352        if entries :
1353            result = [ ("dn", "groupname", "limitby", "balance", "lifetimepaid") ]
1354            for entry in entries :
1355                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid))
1356            return result 
1357       
1358    def extractPayments(self, extractonly={}) :
1359        """Extracts all payment records."""
1360        uname = extractonly.get("username")
1361        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1362        if entries :
1363            result = [ ("username", "amount", "date", "description") ]
1364            for entry in entries :
1365                for (date, amount, description) in entry.Payments :
1366                    result.append((entry.Name, amount, date, description))
1367            return result       
1368       
1369    def extractUpquotas(self, extractonly={}) :
1370        """Extracts all userpquota records."""
1371        pname = extractonly.get("printername")
1372        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1373        if entries :
1374            result = [ ("username", "printername", "dn", "userdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1375            uname = extractonly.get("username")
1376            for entry in entries :
1377                for (user, userpquota) in self.getPrinterUsersAndQuotas(entry, names=[uname or "*"]) :
1378                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit))
1379            return result
1380       
1381    def extractGpquotas(self, extractonly={}) :
1382        """Extracts all grouppquota records."""
1383        pname = extractonly.get("printername")
1384        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1385        if entries :
1386            result = [ ("groupname", "printername", "dn", "groupdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1387            gname = extractonly.get("groupname")
1388            for entry in entries :
1389                for (group, grouppquota) in self.getPrinterGroupsAndQuotas(entry, names=[gname or "*"]) :
1390                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
1391            return result
1392       
1393    def extractUmembers(self, extractonly={}) :
1394        """Extracts all user groups members."""
1395        gname = extractonly.get("groupname")
1396        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1397        if entries :
1398            result = [ ("groupname", "username", "groupdn", "userdn") ]
1399            uname = extractonly.get("username")
1400            for entry in entries :
1401                for member in entry.Members :
1402                    if (uname is None) or (member.Name == uname) :
1403                        result.append((entry.Name, member.Name, entry.ident, member.ident))
1404            return result       
1405               
1406    def extractPmembers(self, extractonly={}) :
1407        """Extracts all printer groups members."""
1408        pname = extractonly.get("printername")
1409        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1410        if entries :
1411            result = [ ("pgroupname", "printername", "pgroupdn", "printerdn") ]
1412            pgname = extractonly.get("pgroupname")
1413            for entry in entries :
1414                for parent in self.getParentPrinters(entry) :
1415                    if (pgname is None) or (parent.Name == pgname) :
1416                        result.append((parent.Name, entry.Name, parent.ident, entry.ident))
1417            return result       
1418       
1419    def extractHistory(self, extractonly={}) :
1420        """Extracts all jobhistory records."""
1421        uname = extractonly.get("username")
1422        if uname :
1423            user = self.getUser(uname)
1424        else :   
1425            user = None
1426        pname = extractonly.get("printername")
1427        if pname :
1428            printer = self.getPrinter(pname)
1429        else :   
1430            printer = None
1431        startdate = extractonly.get("start")
1432        enddate = extractonly.get("end")
1433        (startdate, enddate) = self.cleanDates(startdate, enddate)
1434        entries = self.retrieveHistory(user, printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), limit=None, start=startdate, end=enddate)
1435        if entries :
1436            result = [ ("username", "printername", "dn", "jobid", "pagecounter", "jobsize", "action", "jobdate", "filename", "title", "copies", "options", "jobprice", "hostname", "jobsizebytes", "md5sum", "pages", "billingcode", "precomputedjobsize", "precomputedjobprice") ] 
1437            for entry in entries :
1438                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)) 
1439            return result
1440           
1441    def getBillingCodeFromBackend(self, label) :
1442        """Extracts billing code information given its label : returns first matching billing code."""
1443        code = StorageBillingCode(self, label)
1444        ulabel = self.userCharsetToDatabase(label)
1445        result = self.doSearch("(&(objectClass=pykotaBilling)(pykotaBillingCode=%s))" % ulabel, ["pykotaBillingCode", "pykotaBalance", "pykotaPageCounter", "description"], base=self.info["billingcodebase"])
1446        if result :
1447            fields = result[0][1]       # take only first matching code, ignore the rest
1448            code.ident = result[0][0]
1449            code.BillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [ulabel])[0])
1450            code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1451            code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
1452            code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
1453            code.Exists = 1
1454        return code   
1455       
1456    def addBillingCode(self, label) :
1457        """Adds a billing code to the quota storage, returns it."""
1458        uuid = self.genUUID()
1459        dn = "cn=%s,%s" % (uuid, self.info["billingcodebase"])
1460        fields = { "objectClass" : ["pykotaObject", "pykotaBilling"],
1461                   "cn" : uuid,
1462                   "pykotaBillingCode" : self.userCharsetToDatabase(label),
1463                   "pykotaPageCounter" : "0",
1464                   "pykotaBalance" : "0.0",
1465                 } 
1466        self.doAdd(dn, fields)
1467        return self.getBillingCode(label)
1468       
1469    def writeBillingCodeDescription(self, code) :
1470        """Sets the new description for a billing code."""
1471        fields = {
1472                   "description" : self.userCharsetToDatabase(code.Description or ""), 
1473                 }
1474        if fields["description"] :
1475            self.doModify(code.ident, fields)
1476           
1477    def getMatchingBillingCodes(self, billingcodepattern) :
1478        """Returns the list of all billing codes which match a certain pattern."""
1479        codes = []
1480        result = self.doSearch("(&(objectClass=pykotaBilling)(|%s))" % "".join(["(pykotaBillingCode=%s)" % self.userCharsetToDatabase(bcode) for bcode in billingcodepattern.split(",")]), \
1481                                ["pykotaBillingCode", "description", "pykotaPageCounter", "pykotaBalance"], \
1482                                base=self.info["billingcodebase"])
1483        if result :
1484            for (codeid, fields) in result :
1485                codename = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0])
1486                code = StorageBillingCode(self, codename)
1487                code.ident = codeid
1488                code.BillingCode = codename
1489                code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1490                code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
1491                code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
1492                code.Exists = 1
1493                codes.append(code)
1494                self.cacheEntry("BILLINGCODES", code.BillingCode, code)
1495        return codes       
1496       
1497    def setBillingCodeValues(self, code, newpagecounter, newbalance) :
1498        """Sets the new page counter and balance for a billing code."""
1499        fields = {
1500                   "pykotaPageCounter" : str(newpagecounter),
1501                   "pykotaBalance" : str(newbalance),
1502                 } 
1503        return self.doModify(code.ident, fields)         
1504       
1505    def consumeBillingCode(self, code, pagecounter, balance) :
1506        """Consumes from a billing code."""
1507        fields = {
1508                   "pykotaBalance" : { "operator" : "-", "value" : balance, "convert" : float },
1509                   "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int },
1510                 }
1511        return self.doModify(code.ident, fields)         
Note: See TracBrowser for help on using the browser.