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

Revision 2657, 87.9 kB (checked in by jerome, 18 years ago)

Huge speed improvements when using the --delete command line option for pkprinters, pkbcodes and edpykota.

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