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

Revision 2756, 92.7 kB (checked in by jerome, 18 years ago)

Removed unneeded stuff.

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