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

Revision 2750, 92.9 kB (checked in by jerome, 18 years ago)

Added code to remove an user from an users group in the LDAP backend.

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