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

Revision 2653, 83.2 kB (checked in by jerome, 18 years ago)

Return values translated to user charset instead.

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