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

Revision 2211, 69.1 kB (checked in by jerome, 19 years ago)

Fixed incomplete history retrieval from LDAP

  • 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, 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 os
32import types
33import time
34import md5
35from mx import DateTime
36
37from pykota.storage import PyKotaStorageError, BaseStorage, StorageObject, StorageUser, StorageGroup, StoragePrinter, StorageJob, StorageLastJob, StorageUserPQuota, StorageGroupPQuota
38
39try :
40    import ldap
41    import ldap.modlist
42    import ldap.cidict
43except ImportError :   
44    import sys
45    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0]
46   
47class Storage(BaseStorage) :
48    def __init__(self, pykotatool, host, dbname, user, passwd) :
49        """Opens the LDAP connection."""
50        self.savedtool = pykotatool
51        self.savedhost = host
52        self.saveddbname = dbname
53        self.saveduser = user
54        self.savedpasswd = passwd
55        self.secondStageInit()
56       
57    def secondStageInit(self) :   
58        """Second stage initialisation."""
59        BaseStorage.__init__(self, self.savedtool)
60        self.info = self.tool.config.getLDAPInfo()
61        message = ""
62        for tryit in range(3) :
63            try :
64                self.database = ldap.initialize(self.savedhost) 
65                if self.info["ldaptls"] :
66                    # we want TLS
67                    ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.info["cacert"])
68                    self.database.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
69                    self.database.start_tls_s()
70                self.database.simple_bind_s(self.saveduser, self.savedpasswd)
71                self.basedn = self.saveddbname
72            except ldap.SERVER_DOWN :   
73                message = "LDAP backend for PyKota seems to be down !"
74                self.tool.printInfo("%s" % message, "error")
75                self.tool.printInfo("Trying again in 2 seconds...", "warn")
76                time.sleep(2)
77            except ldap.LDAPError :   
78                message = "Unable to connect to LDAP server %s as %s." % (self.savedhost, self.saveduser)
79                self.tool.printInfo("%s" % message, "error")
80                self.tool.printInfo("Trying again in 2 seconds...", "warn")
81                time.sleep(2)
82            else :   
83                self.useldapcache = self.tool.config.getLDAPCache()
84                if self.useldapcache :
85                    self.tool.logdebug("Low-Level LDAP Caching enabled.")
86                    self.ldapcache = {} # low-level cache specific to LDAP backend
87                self.closed = 0
88                self.tool.logdebug("Database opened (host=%s, dbname=%s, user=%s)" % (self.savedhost, self.saveddbname, self.saveduser))
89                return # All is fine here.
90        raise PyKotaStorageError, message         
91           
92    def close(self) :   
93        """Closes the database connection."""
94        if not self.closed :
95            self.database.unbind_s()
96            self.closed = 1
97            self.tool.logdebug("Database closed.")
98       
99    def genUUID(self) :   
100        """Generates an unique identifier.
101       
102           TODO : this one is not unique accross several print servers, but should be sufficient for testing.
103        """
104        return md5.md5("%s" % time.time()).hexdigest()
105       
106    def normalizeFields(self, fields) :   
107        """Ensure all items are lists."""
108        for (k, v) in fields.items() :
109            if type(v) not in (types.TupleType, types.ListType) :
110                if not v :
111                    del fields[k]
112                else :   
113                    fields[k] = [ v ]
114        return fields       
115       
116    def beginTransaction(self) :   
117        """Starts a transaction."""
118        self.tool.logdebug("Transaction begins... WARNING : No transactions in LDAP !")
119       
120    def commitTransaction(self) :   
121        """Commits a transaction."""
122        self.tool.logdebug("Transaction committed. WARNING : No transactions in LDAP !")
123       
124    def rollbackTransaction(self) :     
125        """Rollbacks a transaction."""
126        self.tool.logdebug("Transaction aborted. WARNING : No transaction in LDAP !")
127       
128    def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE, flushcache=0) :
129        """Does an LDAP search query."""
130        message = ""
131        for tryit in range(3) :
132            try :
133                base = base or self.basedn
134                if self.useldapcache :
135                    # Here we overwrite the fields the app want, to try and
136                    # retrieve ALL user defined attributes ("*")
137                    # + the createTimestamp attribute, needed by job history
138                    #
139                    # This may not work with all LDAP servers
140                    # but works at least in OpenLDAP (2.1.25)
141                    # and iPlanet Directory Server (5.1 SP3)
142                    fields = ["*", "createTimestamp"]         
143                   
144                if self.useldapcache and (not flushcache) and (scope == ldap.SCOPE_BASE) and self.ldapcache.has_key(base) :
145                    entry = self.ldapcache[base]
146                    self.tool.logdebug("LDAP cache hit %s => %s" % (base, entry))
147                    result = [(base, entry)]
148                else :
149                    self.tool.logdebug("QUERY : Filter : %s, BaseDN : %s, Scope : %s, Attributes : %s" % (key, base, scope, fields))
150                    result = self.database.search_s(base, scope, key, fields)
151            except ldap.NO_SUCH_OBJECT, msg :       
152                raise PyKotaStorageError, (_("Search base %s doesn't seem to exist. Probable misconfiguration. Please double check /etc/pykota/pykota.conf : %s") % (base, msg))
153            except ldap.LDAPError, msg :   
154                message = (_("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)) + " : %s" % str(msg)
155                self.tool.printInfo("LDAP error : %s" % message, "error")
156                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
157                self.close()
158                self.secondStageInit()
159            else :     
160                self.tool.logdebug("QUERY : Result : %s" % result)
161                result = [ (dn, ldap.cidict.cidict(attrs)) for (dn, attrs) in result ]
162                if self.useldapcache :
163                    for (dn, attributes) in result :
164                        self.tool.logdebug("LDAP cache store %s => %s" % (dn, attributes))
165                        self.ldapcache[dn] = attributes
166                return result
167        raise PyKotaStorageError, message
168           
169    def doAdd(self, dn, fields) :
170        """Adds an entry in the LDAP directory."""
171        fields = self.normalizeFields(ldap.cidict.cidict(fields))
172        message = ""
173        for tryit in range(3) :
174            try :
175                self.tool.logdebug("QUERY : ADD(%s, %s)" % (dn, str(fields)))
176                entry = ldap.modlist.addModlist(fields)
177                self.tool.logdebug("%s" % entry)
178                self.database.add_s(dn, entry)
179            except ldap.LDAPError, msg :
180                message = (_("Problem adding LDAP entry (%s, %s)") % (dn, str(fields))) + " : %s" % str(msg)
181                self.tool.printInfo("LDAP error : %s" % message, "error")
182                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
183                self.close()
184                self.secondStageInit()
185            else :
186                if self.useldapcache :
187                    self.tool.logdebug("LDAP cache add %s => %s" % (dn, fields))
188                    self.ldapcache[dn] = fields
189                return dn
190        raise PyKotaStorageError, message
191           
192    def doDelete(self, dn) :
193        """Deletes an entry from the LDAP directory."""
194        message = ""
195        for tryit in range(3) :
196            try :
197                self.tool.logdebug("QUERY : Delete(%s)" % dn)
198                self.database.delete_s(dn)
199            except ldap.LDAPError, msg :
200                message = (_("Problem deleting LDAP entry (%s)") % dn) + " : %s" % str(msg)
201                self.tool.printInfo("LDAP error : %s" % message, "error")
202                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
203                self.close()
204                self.secondStageInit()
205            else :   
206                if self.useldapcache :
207                    try :
208                        self.tool.logdebug("LDAP cache del %s" % dn)
209                        del self.ldapcache[dn]
210                    except KeyError :   
211                        pass
212                return       
213        raise PyKotaStorageError, message
214           
215    def doModify(self, dn, fields, ignoreold=1, flushcache=0) :
216        """Modifies an entry in the LDAP directory."""
217        fields = ldap.cidict.cidict(fields)
218        for tryit in range(3) :
219            try :
220                # TODO : take care of, and update LDAP specific cache
221                if self.useldapcache and not flushcache :
222                    if self.ldapcache.has_key(dn) :
223                        old = self.ldapcache[dn]
224                        self.tool.logdebug("LDAP cache hit %s => %s" % (dn, old))
225                        oldentry = {}
226                        for (k, v) in old.items() :
227                            if k != "createTimestamp" :
228                                oldentry[k] = v
229                    else :   
230                        self.tool.logdebug("LDAP cache miss %s" % dn)
231                        oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)[0][1]
232                else :       
233                    oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE, flushcache=flushcache)[0][1]
234                for (k, v) in fields.items() :
235                    if type(v) == type({}) :
236                        try :
237                            oldvalue = v["convert"](oldentry.get(k, [0])[0])
238                        except ValueError :   
239                            self.tool.logdebug("Error converting %s with %s(%s)" % (oldentry.get(k), k, v))
240                            oldvalue = 0
241                        if v["operator"] == '+' :
242                            newvalue = oldvalue + v["value"]
243                        else :   
244                            newvalue = oldvalue - v["value"]
245                        fields[k] = str(newvalue)
246                fields = self.normalizeFields(fields)
247                self.tool.logdebug("QUERY : Modify(%s, %s ==> %s)" % (dn, oldentry, fields))
248                entry = ldap.modlist.modifyModlist(oldentry, fields, ignore_oldexistent=ignoreold)
249                modentry = []
250                for (mop, mtyp, mval) in entry :
251                    if mtyp and (mtyp.lower() != "createtimestamp") :
252                        modentry.append((mop, mtyp, mval))
253                self.tool.logdebug("MODIFY : %s ==> %s ==> %s" % (fields, entry, modentry))
254                if modentry :
255                    self.database.modify_s(dn, modentry)
256            except ldap.LDAPError, msg :
257                message = (_("Problem modifying LDAP entry (%s, %s)") % (dn, fields)) + " : %s" % str(msg)
258                self.tool.printInfo("LDAP error : %s" % message, "error")
259                self.tool.printInfo("LDAP connection will be closed and reopened.", "warn")
260                self.close()
261                self.secondStageInit()
262            else :
263                if self.useldapcache :
264                    cachedentry = self.ldapcache[dn]
265                    for (mop, mtyp, mval) in entry :
266                        if mop in (ldap.MOD_ADD, ldap.MOD_REPLACE) :
267                            cachedentry[mtyp] = mval
268                        else :
269                            try :
270                                del cachedentry[mtyp]
271                            except KeyError :   
272                                pass
273                    self.tool.logdebug("LDAP cache update %s => %s" % (dn, cachedentry))
274                return dn
275        raise PyKotaStorageError, message
276           
277    def filterNames(self, records, attribute) :       
278        """Returns a list of 'attribute' from a list of records.
279       
280           Logs any missing attribute.
281        """   
282        result = []
283        for (dn, record) in records :
284            attrval = record.get(attribute, [None])[0]
285            if attrval is None :
286                self.tool.printInfo("Object %s has no %s attribute !" % (dn, attribute), "error")
287            else :   
288                result.append(attrval)
289        return result       
290               
291    def getAllPrintersNames(self, printername=None) :   
292        """Extracts all printer names or only the printers' names matching the optional parameter."""
293        printernames = []
294        ldapfilter = "objectClass=pykotaPrinter"
295        if printername :
296            ldapfilter = "(&(%s)(pykotaPrinterName=%s))" % (ldapfilter, printername)
297        result = self.doSearch(ldapfilter, ["pykotaPrinterName"], base=self.info["printerbase"])
298        if result :
299            printernames = self.filterNames(result, "pykotaPrinterName")
300        return printernames
301       
302    def getAllUsersNames(self, username=None) :   
303        """Extracts all user names or only the users' names matching the optional parameter."""
304        usernames = []
305        ldapfilter = "objectClass=pykotaAccount"
306        if username :
307            ldapfilter = "(&(%s)(pykotaUserName=%s))" % (ldapfilter, username)
308        result = self.doSearch(ldapfilter, ["pykotaUserName"], base=self.info["userbase"])
309        if result :
310            usernames = self.filterNames(result, "pykotaUserName")
311        return usernames
312       
313    def getAllGroupsNames(self, groupname=None) :   
314        """Extracts all group names or only the groups' names matching the optional parameter."""
315        groupnames = []
316        ldapfilter = "objectClass=pykotaGroup"
317        if groupname :
318            ldapfilter = "(&(%s)(pykotaGroupName=%s))" % (ldapfilter, groupname)
319        result = self.doSearch(ldapfilter, ["pykotaGroupName"], base=self.info["groupbase"])
320        if result :
321            groupnames = self.filterNames(result, "pykotaGroupName")
322        return groupnames
323       
324    def getUserNbJobsFromHistory(self, user) :
325        """Returns the number of jobs the user has in history."""
326        result = self.doSearch("(&(pykotaUserName=%s)(objectClass=pykotaJob))" % user.Name, None, base=self.info["jobbase"])
327        return len(result)
328       
329    def getUserFromBackend(self, username) :   
330        """Extracts user information given its name."""
331        user = StorageUser(self, username)
332        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "pykotaOverCharge"], base=self.info["userbase"])
333        if result :
334            fields = result[0][1]
335            user.ident = result[0][0]
336            user.Name = fields.get("pykotaUserName", [username])[0] 
337            user.Email = fields.get(self.info["usermail"], [None])[0]
338            user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
339            user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
340            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["balancerdn"], username), ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments"], base=self.info["balancebase"])
341            if not result :
342                raise PyKotaStorageError, _("No pykotaAccountBalance object found for user %s. Did you create LDAP entries manually ?") % username
343            else :
344                fields = result[0][1]
345                user.idbalance = result[0][0]
346                user.AccountBalance = fields.get("pykotaBalance")
347                if user.AccountBalance is not None :
348                    if user.AccountBalance[0].upper() == "NONE" :
349                        user.AccountBalance = None
350                    else :   
351                        user.AccountBalance = float(user.AccountBalance[0])
352                user.AccountBalance = user.AccountBalance or 0.0       
353                user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
354                if user.LifeTimePaid is not None :
355                    if user.LifeTimePaid[0].upper() == "NONE" :
356                        user.LifeTimePaid = None
357                    else :   
358                        user.LifeTimePaid = float(user.LifeTimePaid[0])
359                user.LifeTimePaid = user.LifeTimePaid or 0.0       
360                user.Payments = []
361                for payment in fields.get("pykotaPayments", []) :
362                    (date, amount) = payment.split(" # ")
363                    user.Payments.append((date, amount))
364            user.Exists = 1
365        return user
366       
367    def getGroupFromBackend(self, groupname) :   
368        """Extracts group information given its name."""
369        group = StorageGroup(self, groupname)
370        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), ["pykotaGroupName", "pykotaLimitBy"], base=self.info["groupbase"])
371        if result :
372            fields = result[0][1]
373            group.ident = result[0][0]
374            group.Name = fields.get("pykotaGroupName", [groupname])[0] 
375            group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
376            group.AccountBalance = 0.0
377            group.LifeTimePaid = 0.0
378            for member in self.getGroupMembers(group) :
379                if member.Exists :
380                    group.AccountBalance += member.AccountBalance
381                    group.LifeTimePaid += member.LifeTimePaid
382            group.Exists = 1
383        return group
384       
385    def getPrinterFromBackend(self, printername) :       
386        """Extracts printer information given its name : returns first matching printer."""
387        printer = StoragePrinter(self, printername)
388        result = self.doSearch("(&(objectClass=pykotaPrinter)(|(pykotaPrinterName=%s)(%s=%s)))" % (printername, self.info["printerrdn"], printername), ["pykotaPrinterName", "pykotaPricePerPage", "pykotaPricePerJob", "uniqueMember", "description"], base=self.info["printerbase"])
389        if result :
390            fields = result[0][1]       # take only first matching printer, ignore the rest
391            printer.ident = result[0][0]
392            printer.Name = fields.get("pykotaPrinterName", [printername])[0] 
393            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0])
394            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0])
395            printer.uniqueMember = fields.get("uniqueMember", [])
396            printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
397            printer.Exists = 1
398        return printer   
399       
400    def getUserPQuotaFromBackend(self, user, printer) :       
401        """Extracts a user print quota."""
402        userpquota = StorageUserPQuota(self, user, printer)
403        if printer.Exists and user.Exists :
404            if self.info["userquotabase"].lower() == "user" :
405                base = user.ident
406            else :   
407                base = self.info["userquotabase"]
408            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % (user.Name, printer.Name), ["pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], base=base)
409            if result :
410                fields = result[0][1]
411                userpquota.ident = result[0][0]
412                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
413                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
414                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
415                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
416                if userpquota.SoftLimit is not None :
417                    if userpquota.SoftLimit[0].upper() == "NONE" :
418                        userpquota.SoftLimit = None
419                    else :   
420                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
421                userpquota.HardLimit = fields.get("pykotaHardLimit")
422                if userpquota.HardLimit is not None :
423                    if userpquota.HardLimit[0].upper() == "NONE" :
424                        userpquota.HardLimit = None
425                    elif userpquota.HardLimit is not None :   
426                        userpquota.HardLimit = int(userpquota.HardLimit[0])
427                userpquota.DateLimit = fields.get("pykotaDateLimit")
428                if userpquota.DateLimit is not None :
429                    if userpquota.DateLimit[0].upper() == "NONE" : 
430                        userpquota.DateLimit = None
431                    else :   
432                        userpquota.DateLimit = userpquota.DateLimit[0]
433                userpquota.Exists = 1
434        return userpquota
435       
436    def getGroupPQuotaFromBackend(self, group, printer) :       
437        """Extracts a group print quota."""
438        grouppquota = StorageGroupPQuota(self, group, printer)
439        if group.Exists :
440            if self.info["groupquotabase"].lower() == "group" :
441                base = group.ident
442            else :   
443                base = self.info["groupquotabase"]
444            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % (group.Name, printer.Name), ["pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=base)
445            if result :
446                fields = result[0][1]
447                grouppquota.ident = result[0][0]
448                grouppquota.SoftLimit = fields.get("pykotaSoftLimit")
449                if grouppquota.SoftLimit is not None :
450                    if grouppquota.SoftLimit[0].upper() == "NONE" :
451                        grouppquota.SoftLimit = None
452                    else :   
453                        grouppquota.SoftLimit = int(grouppquota.SoftLimit[0])
454                grouppquota.HardLimit = fields.get("pykotaHardLimit")
455                if grouppquota.HardLimit is not None :
456                    if grouppquota.HardLimit[0].upper() == "NONE" :
457                        grouppquota.HardLimit = None
458                    else :   
459                        grouppquota.HardLimit = int(grouppquota.HardLimit[0])
460                grouppquota.DateLimit = fields.get("pykotaDateLimit")
461                if grouppquota.DateLimit is not None :
462                    if grouppquota.DateLimit[0].upper() == "NONE" : 
463                        grouppquota.DateLimit = None
464                    else :   
465                        grouppquota.DateLimit = grouppquota.DateLimit[0]
466                grouppquota.PageCounter = 0
467                grouppquota.LifePageCounter = 0
468                usernamesfilter = "".join(["(pykotaUserName=%s)" % member.Name for member in self.getGroupMembers(group)])
469                if usernamesfilter :
470                    usernamesfilter = "(|%s)" % usernamesfilter
471                if self.info["userquotabase"].lower() == "user" :
472                    base = self.info["userbase"]
473                else :
474                    base = self.info["userquotabase"]
475                result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)%s)" % (printer.Name, usernamesfilter), ["pykotaPageCounter", "pykotaLifePageCounter"], base=base)
476                if result :
477                    for userpquota in result :   
478                        grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0)
479                        grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0)
480                grouppquota.Exists = 1
481        return grouppquota
482       
483    def getPrinterLastJobFromBackend(self, printer) :       
484        """Extracts a printer's last job information."""
485        lastjob = StorageLastJob(self, printer)
486        result = self.doSearch("(&(objectClass=pykotaLastjob)(|(pykotaPrinterName=%s)(%s=%s)))" % (printer.Name, self.info["printerrdn"], printer.Name), ["pykotaLastJobIdent"], base=self.info["lastjobbase"])
487        if result :
488            lastjob.lastjobident = result[0][0]
489            lastjobident = result[0][1]["pykotaLastJobIdent"][0]
490            result = None
491            try :
492                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes", 
493                                                                  "pykotaHostName", 
494                                                                  "pykotaUserName", 
495                                                                  "pykotaPrinterName", 
496                                                                  "pykotaJobId", 
497                                                                  "pykotaPrinterPageCounter", 
498                                                                  "pykotaJobSize", 
499                                                                  "pykotaAction", 
500                                                                  "pykotaJobPrice", 
501                                                                  "pykotaFileName", 
502                                                                  "pykotaTitle", 
503                                                                  "pykotaCopies", 
504                                                                  "pykotaOptions", 
505                                                                  "pykotaBillingCode", 
506                                                                  "pykotaPages", 
507                                                                  "pykotaMD5Sum", 
508                                                                  "createTimestamp" ], 
509                                                                base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
510            except PyKotaStorageError :   
511                pass # Last job entry exists, but job probably doesn't exist anymore.
512            if result :
513                fields = result[0][1]
514                lastjob.ident = result[0][0]
515                lastjob.JobId = fields.get("pykotaJobId")[0]
516                lastjob.UserName = fields.get("pykotaUserName")[0]
517                lastjob.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0])
518                try :
519                    lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0])
520                except ValueError :   
521                    lastjob.JobSize = None
522                try :   
523                    lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
524                except ValueError :   
525                    lastjob.JobPrice = None
526                lastjob.JobAction = fields.get("pykotaAction", [""])[0]
527                lastjob.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
528                lastjob.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
529                lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0])
530                lastjob.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
531                lastjob.JobHostName = fields.get("pykotaHostName", [""])[0]
532                lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
533                lastjob.JobBillingCode = fields.get("pykotaBillingCode", [None])[0]
534                lastjob.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
535                lastjob.JobPages = fields.get("pykotaPages", [""])[0]
536                date = fields.get("createTimestamp", ["19700101000000"])[0]
537                year = int(date[:4])
538                month = int(date[4:6])
539                day = int(date[6:8])
540                hour = int(date[8:10])
541                minute = int(date[10:12])
542                second = int(date[12:14])
543                lastjob.JobDate = "%04i-%02i-%02i %02i:%02i:%02i" % (year, month, day, hour, minute, second)
544                lastjob.Exists = 1
545        return lastjob
546       
547    def getGroupMembersFromBackend(self, group) :       
548        """Returns the group's members list."""
549        groupmembers = []
550        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (group.Name, self.info["grouprdn"], group.Name), [self.info["groupmembers"]], base=self.info["groupbase"])
551        if result :
552            for username in result[0][1].get(self.info["groupmembers"], []) :
553                groupmembers.append(self.getUser(username))
554        return groupmembers       
555       
556    def getUserGroupsFromBackend(self, user) :       
557        """Returns the user's groups list."""
558        groups = []
559        result = self.doSearch("(&(objectClass=pykotaGroup)(%s=%s))" % (self.info["groupmembers"], user.Name), [self.info["grouprdn"], "pykotaGroupName", "pykotaLimitBy"], base=self.info["groupbase"])
560        if result :
561            for (groupid, fields) in result :
562                groupname = (fields.get("pykotaGroupName", [None]) or fields.get(self.info["grouprdn"], [None]))[0]
563                group = self.getFromCache("GROUPS", groupname)
564                if group is None :
565                    group = StorageGroup(self, groupname)
566                    group.ident = groupid
567                    group.LimitBy = fields.get("pykotaLimitBy")
568                    if group.LimitBy is not None :
569                        group.LimitBy = group.LimitBy[0]
570                    else :   
571                        group.LimitBy = "quota"
572                    group.AccountBalance = 0.0
573                    group.LifeTimePaid = 0.0
574                    for member in self.getGroupMembers(group) :
575                        if member.Exists :
576                            group.AccountBalance += member.AccountBalance
577                            group.LifeTimePaid += member.LifeTimePaid
578                    group.Exists = 1
579                    self.cacheEntry("GROUPS", group.Name, group)
580                groups.append(group)
581        return groups       
582       
583    def getParentPrintersFromBackend(self, printer) :   
584        """Get all the printer groups this printer is a member of."""
585        pgroups = []
586        result = self.doSearch("(&(objectClass=pykotaPrinter)(uniqueMember=%s))" % printer.ident, ["pykotaPrinterName"], base=self.info["printerbase"])
587        if result :
588            for (printerid, fields) in result :
589                if printerid != printer.ident : # In case of integrity violation.
590                    parentprinter = self.getPrinter(fields.get("pykotaPrinterName")[0])
591                    if parentprinter.Exists :
592                        pgroups.append(parentprinter)
593        return pgroups
594       
595    def getMatchingPrinters(self, printerpattern) :
596        """Returns the list of all printers for which name matches a certain pattern."""
597        printers = []
598        # see comment at the same place in pgstorage.py
599        result = self.doSearch("(&(objectClass=pykotaPrinter)(|%s))" % "".join(["(pykotaPrinterName=%s)(%s=%s)" % (pname, self.info["printerrdn"], pname) for pname in printerpattern.split(",")]), ["pykotaPrinterName", "pykotaPricePerPage", "pykotaPricePerJob", "uniqueMember", "description"], base=self.info["printerbase"])
600        if result :
601            for (printerid, fields) in result :
602                printername = fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0]
603                printer = StoragePrinter(self, printername)
604                printer.ident = printerid
605                printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
606                printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
607                printer.uniqueMember = fields.get("uniqueMember", [])
608                printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
609                printer.Exists = 1
610                printers.append(printer)
611                self.cacheEntry("PRINTERS", printer.Name, printer)
612        return printers       
613       
614    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :       
615        """Returns the list of users who uses a given printer, along with their quotas."""
616        usersandquotas = []
617        if self.info["userquotabase"].lower() == "user" :
618           base = self.info["userbase"]
619        else :
620           base = self.info["userquotabase"]
621        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaUserName=%s)" % uname for uname in names])), ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], base=base)
622        if result :
623            for (userquotaid, fields) in result :
624                user = self.getUser(fields.get("pykotaUserName")[0])
625                userpquota = StorageUserPQuota(self, user, printer)
626                userpquota.ident = userquotaid
627                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
628                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
629                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
630                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
631                if userpquota.SoftLimit is not None :
632                    if userpquota.SoftLimit[0].upper() == "NONE" :
633                        userpquota.SoftLimit = None
634                    else :   
635                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
636                userpquota.HardLimit = fields.get("pykotaHardLimit")
637                if userpquota.HardLimit is not None :
638                    if userpquota.HardLimit[0].upper() == "NONE" :
639                        userpquota.HardLimit = None
640                    elif userpquota.HardLimit is not None :   
641                        userpquota.HardLimit = int(userpquota.HardLimit[0])
642                userpquota.DateLimit = fields.get("pykotaDateLimit")
643                if userpquota.DateLimit is not None :
644                    if userpquota.DateLimit[0].upper() == "NONE" : 
645                        userpquota.DateLimit = None
646                    else :   
647                        userpquota.DateLimit = userpquota.DateLimit[0]
648                userpquota.Exists = 1
649                usersandquotas.append((user, userpquota))
650                self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
651        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
652        return usersandquotas
653               
654    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :       
655        """Returns the list of groups which uses a given printer, along with their quotas."""
656        groupsandquotas = []
657        if self.info["groupquotabase"].lower() == "group" :
658           base = self.info["groupbase"]
659        else :
660           base = self.info["groupquotabase"]
661        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaGroupName=%s)" % gname for gname in names])), ["pykotaGroupName"], base=base)
662        if result :
663            for (groupquotaid, fields) in result :
664                group = self.getGroup(fields.get("pykotaGroupName")[0])
665                grouppquota = self.getGroupPQuota(group, printer)
666                groupsandquotas.append((group, grouppquota))
667        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))           
668        return groupsandquotas
669       
670    def addPrinter(self, printername) :       
671        """Adds a printer to the quota storage, returns it."""
672        fields = { self.info["printerrdn"] : printername,
673                   "objectClass" : ["pykotaObject", "pykotaPrinter"],
674                   "cn" : printername,
675                   "pykotaPrinterName" : printername,
676                   "pykotaPricePerPage" : "0.0",
677                   "pykotaPricePerJob" : "0.0",
678                 } 
679        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
680        self.doAdd(dn, fields)
681        return self.getPrinter(printername)
682       
683    def addUser(self, user) :       
684        """Adds a user to the quota storage, returns it."""
685        newfields = {
686                       "pykotaUserName" : user.Name,
687                       "pykotaLimitBy" : (user.LimitBy or "quota"),
688                       "pykotaOverCharge" : str(user.OverCharge),
689                    }   
690                       
691        if user.Email :
692            newfields.update({self.info["usermail"]: user.Email})
693        mustadd = 1
694        if self.info["newuser"].lower() != 'below' :
695            try :
696                (where, action) = [s.strip() for s in self.info["newuser"].split(",")]
697            except ValueError :
698                (where, action) = (self.info["newuser"].strip(), "fail")
699            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % (where, self.info["userrdn"], user.Name), None, base=self.info["userbase"])
700            if result :
701                (dn, fields) = result[0]
702                oc = fields.get("objectClass", fields.get("objectclass", []))
703                oc.extend(["pykotaAccount", "pykotaAccountBalance"])
704                fields.update(newfields)
705                fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0),
706                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })   
707                self.doModify(dn, fields)
708                mustadd = 0
709            else :
710                message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name)
711                if action.lower() == "warn" :   
712                    self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn")
713                else : # 'fail' or incorrect setting
714                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
715               
716        if mustadd :
717            if self.info["userbase"] == self.info["balancebase"] :           
718                fields = { self.info["userrdn"] : user.Name,
719                           "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
720                           "cn" : user.Name,
721                           "pykotaBalance" : str(user.AccountBalance or 0.0),
722                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
723                         } 
724            else :             
725                fields = { self.info["userrdn"] : user.Name,
726                           "objectClass" : ["pykotaObject", "pykotaAccount"],
727                           "cn" : user.Name,
728                         } 
729            fields.update(newfields)         
730            dn = "%s=%s,%s" % (self.info["userrdn"], user.Name, self.info["userbase"])
731            self.doAdd(dn, fields)
732            if self.info["userbase"] != self.info["balancebase"] :           
733                fields = { self.info["balancerdn"] : user.Name,
734                           "objectClass" : ["pykotaObject", "pykotaAccountBalance"],
735                           "cn" : user.Name,
736                           "pykotaBalance" : str(user.AccountBalance or 0.0),
737                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
738                         } 
739                dn = "%s=%s,%s" % (self.info["balancerdn"], user.Name, self.info["balancebase"])
740                self.doAdd(dn, fields)
741           
742        return self.getUser(user.Name)
743       
744    def addGroup(self, group) :       
745        """Adds a group to the quota storage, returns it."""
746        newfields = { 
747                      "pykotaGroupName" : group.Name,
748                      "pykotaLimitBy" : (group.LimitBy or "quota"),
749                    } 
750        mustadd = 1
751        if self.info["newgroup"].lower() != 'below' :
752            try :
753                (where, action) = [s.strip() for s in self.info["newgroup"].split(",")]
754            except ValueError :
755                (where, action) = (self.info["newgroup"].strip(), "fail")
756            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % (where, self.info["grouprdn"], group.Name), None, base=self.info["groupbase"])
757            if result :
758                (dn, fields) = result[0]
759                oc = fields.get("objectClass", fields.get("objectclass", []))
760                oc.extend(["pykotaGroup"])
761                fields.update(newfields)
762                self.doModify(dn, fields)
763                mustadd = 0
764            else :
765                message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name
766                if action.lower() == "warn" :   
767                    self.tool.printInfo("%s. A new entry will be created instead." % message, "warn")
768                else : # 'fail' or incorrect setting
769                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
770               
771        if mustadd :
772            fields = { self.info["grouprdn"] : group.Name,
773                       "objectClass" : ["pykotaObject", "pykotaGroup"],
774                       "cn" : group.Name,
775                     } 
776            fields.update(newfields)         
777            dn = "%s=%s,%s" % (self.info["grouprdn"], group.Name, self.info["groupbase"])
778            self.doAdd(dn, fields)
779        return self.getGroup(group.Name)
780       
781    def addUserToGroup(self, user, group) :   
782        """Adds an user to a group."""
783        if user.Name not in [u.Name for u in self.getGroupMembers(group)] :
784            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
785            if result :
786                fields = result[0][1]
787                if not fields.has_key(self.info["groupmembers"]) :
788                    fields[self.info["groupmembers"]] = []
789                fields[self.info["groupmembers"]].append(user.Name)
790                self.doModify(group.ident, fields)
791                group.Members.append(user)
792               
793    def addUserPQuota(self, user, printer) :
794        """Initializes a user print quota on a printer."""
795        uuid = self.genUUID()
796        fields = { "cn" : uuid,
797                   "objectClass" : ["pykotaObject", "pykotaUserPQuota"],
798                   "pykotaUserName" : user.Name,
799                   "pykotaPrinterName" : printer.Name,
800                   "pykotaDateLimit" : "None",
801                   "pykotaPageCounter" : "0",
802                   "pykotaLifePageCounter" : "0",
803                   "pykotaWarnCount" : "0",
804                 } 
805        if self.info["userquotabase"].lower() == "user" :
806            dn = "cn=%s,%s" % (uuid, user.ident)
807        else :   
808            dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
809        self.doAdd(dn, fields)
810        return self.getUserPQuota(user, printer)
811       
812    def addGroupPQuota(self, group, printer) :
813        """Initializes a group print quota on a printer."""
814        uuid = self.genUUID()
815        fields = { "cn" : uuid,
816                   "objectClass" : ["pykotaObject", "pykotaGroupPQuota"],
817                   "pykotaGroupName" : group.Name,
818                   "pykotaPrinterName" : printer.Name,
819                   "pykotaDateLimit" : "None",
820                 } 
821        if self.info["groupquotabase"].lower() == "group" :
822            dn = "cn=%s,%s" % (uuid, group.ident)
823        else :   
824            dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
825        self.doAdd(dn, fields)
826        return self.getGroupPQuota(group, printer)
827       
828    def writePrinterPrices(self, printer) :   
829        """Write the printer's prices back into the storage."""
830        fields = {
831                   "pykotaPricePerPage" : str(printer.PricePerPage),
832                   "pykotaPricePerJob" : str(printer.PricePerJob),
833                 }
834        self.doModify(printer.ident, fields)
835       
836    def writePrinterDescription(self, printer) :   
837        """Write the printer's description back into the storage."""
838        fields = {
839                   "description" : self.userCharsetToDatabase(str(printer.Description)), 
840                 }
841        self.doModify(printer.ident, fields)
842       
843    def writeUserOverCharge(self, user, factor) :
844        """Sets the user's overcharging coefficient."""
845        fields = {
846                   "pykotaOverCharge" : str(factor),
847                 }
848        self.doModify(user.ident, fields)
849       
850    def writeUserLimitBy(self, user, limitby) :   
851        """Sets the user's limiting factor."""
852        fields = {
853                   "pykotaLimitBy" : limitby,
854                 }
855        self.doModify(user.ident, fields)         
856       
857    def writeGroupLimitBy(self, group, limitby) :   
858        """Sets the group's limiting factor."""
859        fields = {
860                   "pykotaLimitBy" : limitby,
861                 }
862        self.doModify(group.ident, fields)         
863       
864    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :   
865        """Sets the date limit permanently for a user print quota."""
866        fields = {
867                   "pykotaDateLimit" : datelimit,
868                 }
869        return self.doModify(userpquota.ident, fields)
870           
871    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :   
872        """Sets the date limit permanently for a group print quota."""
873        fields = {
874                   "pykotaDateLimit" : datelimit,
875                 }
876        return self.doModify(grouppquota.ident, fields)
877       
878    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :   
879        """Increase page counters for a user print quota."""
880        fields = {
881                   "pykotaPageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
882                   "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
883                 }
884        return self.doModify(userpquota.ident, fields)         
885       
886    def writeUserPQuotaPagesCounters(self, userpquota, newpagecounter, newlifepagecounter) :   
887        """Sets the new page counters permanently for a user print quota."""
888        fields = {
889                   "pykotaPageCounter" : str(newpagecounter),
890                   "pykotaLifePageCounter" : str(newlifepagecounter),
891                   "pykotaDateLimit" : None,
892                   "pykotaWarnCount" : "0",
893                 } 
894        return self.doModify(userpquota.ident, fields)         
895       
896    def decreaseUserAccountBalance(self, user, amount) :   
897        """Decreases user's account balance from an amount."""
898        fields = {
899                   "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float },
900                 }
901        return self.doModify(user.idbalance, fields, flushcache=1)         
902       
903    def writeUserAccountBalance(self, user, newbalance, newlifetimepaid=None) :   
904        """Sets the new account balance and eventually new lifetime paid."""
905        fields = {
906                   "pykotaBalance" : str(newbalance),
907                 }
908        if newlifetimepaid is not None :
909            fields.update({ "pykotaLifeTimePaid" : str(newlifetimepaid) })
910        return self.doModify(user.idbalance, fields)         
911           
912    def writeNewPayment(self, user, amount) :       
913        """Adds a new payment to the payments history."""
914        payments = []
915        for payment in user.Payments :
916            payments.append("%s # %s" % (payment[0], str(payment[1])))
917        payments.append("%s # %s" % (str(DateTime.now()), str(amount)))
918        fields = {
919                   "pykotaPayments" : payments,
920                 }
921        return self.doModify(user.idbalance, fields)         
922       
923    def writeLastJobSize(self, lastjob, jobsize, jobprice) :       
924        """Sets the last job's size permanently."""
925        fields = {
926                   "pykotaJobSize" : str(jobsize),
927                   "pykotaJobPrice" : str(jobprice),
928                 }
929        self.doModify(lastjob.ident, fields)         
930       
931    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) :
932        """Adds a job in a printer's history."""
933        if (not self.disablehistory) or (not printer.LastJob.Exists) :
934            uuid = self.genUUID()
935            dn = "cn=%s,%s" % (uuid, self.info["jobbase"])
936        else :   
937            uuid = printer.LastJob.ident[3:].split(",")[0]
938            dn = printer.LastJob.ident
939        if self.privacy :   
940            # For legal reasons, we want to hide the title, filename and options
941            title = filename = options = "Hidden because of privacy concerns"
942        fields = {
943                   "objectClass" : ["pykotaObject", "pykotaJob"],
944                   "cn" : uuid,
945                   "pykotaUserName" : user.Name,
946                   "pykotaPrinterName" : printer.Name,
947                   "pykotaJobId" : jobid,
948                   "pykotaPrinterPageCounter" : str(pagecounter),
949                   "pykotaAction" : action,
950                   "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename), 
951                   "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title), 
952                   "pykotaCopies" : str(copies), 
953                   "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options), 
954                   "pykotaHostName" : str(clienthost), 
955                   "pykotaJobSizeBytes" : str(jobsizebytes),
956                   "pykotaMD5Sum" : str(jobmd5sum),
957                 }
958        if (not self.disablehistory) or (not printer.LastJob.Exists) :
959            if jobsize is not None :         
960                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
961            self.doAdd(dn, fields)
962        else :   
963            # here we explicitly want to reset jobsize to 'None' if needed
964            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
965            self.doModify(dn, fields)
966           
967        if printer.LastJob.Exists :
968            fields = {
969                       "pykotaLastJobIdent" : uuid,
970                     }
971            self.doModify(printer.LastJob.lastjobident, fields)         
972        else :   
973            lastjuuid = self.genUUID()
974            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
975            fields = {
976                       "objectClass" : ["pykotaObject", "pykotaLastJob"],
977                       "cn" : lastjuuid,
978                       "pykotaPrinterName" : printer.Name,
979                       "pykotaLastJobIdent" : uuid,
980                     } 
981            self.doAdd(lastjdn, fields)         
982           
983    def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
984        """Sets soft and hard limits for a user quota."""
985        fields = { 
986                   "pykotaSoftLimit" : str(softlimit),
987                   "pykotaHardLimit" : str(hardlimit),
988                   "pykotaDateLimit" : "None",
989                   "pykotaWarnCount" : "0",
990                 }
991        self.doModify(userpquota.ident, fields)
992       
993    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
994        """Sets the warn counter value for a user quota."""
995        fields = { 
996                   "pykotaWarnCount" : str(warncount or 0),
997                 }
998        self.doModify(userpquota.ident, fields)
999       
1000    def increaseUserPQuotaWarnCount(self, userpquota) :
1001        """Increases the warn counter value for a user quota."""
1002        fields = {
1003                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
1004                 }
1005        return self.doModify(userpquota.ident, fields)         
1006       
1007    def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
1008        """Sets soft and hard limits for a group quota on a specific printer."""
1009        fields = { 
1010                   "pykotaSoftLimit" : str(softlimit),
1011                   "pykotaHardLimit" : str(hardlimit),
1012                   "pykotaDateLimit" : "None",
1013                 }
1014        self.doModify(grouppquota.ident, fields)
1015           
1016    def writePrinterToGroup(self, pgroup, printer) :
1017        """Puts a printer into a printer group."""
1018        if printer.ident not in pgroup.uniqueMember :
1019            pgroup.uniqueMember.append(printer.ident)
1020            fields = {
1021                       "uniqueMember" : pgroup.uniqueMember
1022                     } 
1023            self.doModify(pgroup.ident, fields)         
1024           
1025    def removePrinterFromGroup(self, pgroup, printer) :
1026        """Removes a printer from a printer group."""
1027        try :
1028            pgroup.uniqueMember.remove(printer.ident)
1029        except ValueError :   
1030            pass
1031        else :   
1032            fields = {
1033                       "uniqueMember" : pgroup.uniqueMember,
1034                     } 
1035            self.doModify(pgroup.ident, fields)         
1036           
1037    def retrieveHistory(self, user=None, printer=None, datelimit=None, hostname=None, limit=100) :   
1038        """Retrieves all print jobs for user on printer (or all) before date, limited to first 100 results."""
1039        precond = "(objectClass=pykotaJob)"
1040        where = []
1041        if (user is not None) and user.Exists :
1042            where.append("(pykotaUserName=%s)" % user.Name)
1043        if (printer is not None) and printer.Exists :
1044            where.append("(pykotaPrinterName=%s)" % printer.Name)
1045        if hostname is not None :
1046            where.append("(pykotaHostName=%s)" % hostname)
1047        if where :   
1048            where = "(&%s)" % "".join([precond] + where)
1049        else :   
1050            where = precond
1051        jobs = []   
1052        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
1053                                               "pykotaHostName", 
1054                                               "pykotaUserName", 
1055                                               "pykotaPrinterName", 
1056                                               "pykotaJobId", 
1057                                               "pykotaPrinterPageCounter", 
1058                                               "pykotaAction", 
1059                                               "pykotaJobSize", 
1060                                               "pykotaJobPrice", 
1061                                               "pykotaFileName", 
1062                                               "pykotaTitle", 
1063                                               "pykotaCopies", 
1064                                               "pykotaOptions", 
1065                                               "pykotaBillingCode", 
1066                                               "pykotaPages", 
1067                                               "pykotaMD5Sum", 
1068                                               "createTimestamp" ], 
1069                                      base=self.info["jobbase"])
1070        if result :
1071            for (ident, fields) in result :
1072                job = StorageJob(self)
1073                job.ident = ident
1074                job.JobId = fields.get("pykotaJobId")[0]
1075                job.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
1076                try :
1077                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
1078                except ValueError :   
1079                    job.JobSize = None
1080                try :   
1081                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
1082                except ValueError :
1083                    job.JobPrice = None
1084                job.JobAction = fields.get("pykotaAction", [""])[0]
1085                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
1086                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
1087                job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
1088                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
1089                job.JobHostName = fields.get("pykotaHostName", [""])[0]
1090                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
1091                job.JobBillingCode = fields.get("pykotaBillingCode", [None])[0]
1092                job.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
1093                job.JobPages = fields.get("pykotaPages", [""])[0]
1094                date = fields.get("createTimestamp", ["19700101000000"])[0]
1095                year = int(date[:4])
1096                month = int(date[4:6])
1097                day = int(date[6:8])
1098                hour = int(date[8:10])
1099                minute = int(date[10:12])
1100                second = int(date[12:14])
1101                job.JobDate = "%04i-%02i-%02i %02i:%02i:%02i" % (year, month, day, hour, minute, second)
1102                if (datelimit is None) or (job.JobDate <= datelimit) :
1103                    job.UserName = fields.get("pykotaUserName")[0]
1104                    job.PrinterName = fields.get("pykotaPrinterName")[0]
1105                    job.Exists = 1
1106                    jobs.append(job)
1107            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))       
1108            if limit :   
1109                jobs = jobs[:int(limit)]
1110        return jobs
1111       
1112    def deleteUser(self, user) :   
1113        """Completely deletes an user from the Quota Storage."""
1114        todelete = []   
1115        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % user.Name, base=self.info["jobbase"])
1116        for (ident, fields) in result :
1117            todelete.append(ident)
1118        if self.info["userquotabase"].lower() == "user" :
1119            base = self.info["userbase"]
1120        else :
1121            base = self.info["userquotabase"]
1122        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s))" % user.Name, ["pykotaPrinterName", "pykotaUserName"], base=base)
1123        for (ident, fields) in result :
1124            # ensure the user print quota entry will be deleted
1125            todelete.append(ident)
1126           
1127            # if last job of current printer was printed by the user
1128            # to delete, we also need to delete the printer's last job entry.
1129            printername = fields["pykotaPrinterName"][0]
1130            printer = self.getPrinter(printername)
1131            if printer.LastJob.UserName == user.Name :
1132                todelete.append(printer.LastJob.lastjobident)
1133           
1134        for ident in todelete :   
1135            self.doDelete(ident)
1136           
1137        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)   
1138        if result :
1139            fields = result[0][1]
1140            for k in fields.keys() :
1141                if k.startswith("pykota") :
1142                    del fields[k]
1143                elif k.lower() == "objectclass" :   
1144                    todelete = []
1145                    for i in range(len(fields[k])) :
1146                        if fields[k][i].startswith("pykota") : 
1147                            todelete.append(i)
1148                    todelete.sort()       
1149                    todelete.reverse()
1150                    for i in todelete :
1151                        del fields[k][i]
1152            if fields.get("objectClass") or fields.get("objectclass") :
1153                self.doModify(user.ident, fields, ignoreold=0)       
1154            else :   
1155                self.doDelete(user.ident)
1156        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % user.Name, ["pykotaUserName"], base=self.info["balancebase"])
1157        for (ident, fields) in result :
1158            self.doDelete(ident)
1159       
1160    def deleteGroup(self, group) :   
1161        """Completely deletes a group from the Quota Storage."""
1162        if self.info["groupquotabase"].lower() == "group" :
1163            base = self.info["groupbase"]
1164        else :
1165            base = self.info["groupquotabase"]
1166        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s))" % group.Name, ["pykotaGroupName"], base=base)
1167        for (ident, fields) in result :
1168            self.doDelete(ident)
1169        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
1170        if result :
1171            fields = result[0][1]
1172            for k in fields.keys() :
1173                if k.startswith("pykota") :
1174                    del fields[k]
1175                elif k.lower() == "objectclass" :   
1176                    todelete = []
1177                    for i in range(len(fields[k])) :
1178                        if fields[k][i].startswith("pykota") : 
1179                            todelete.append(i)
1180                    todelete.sort()       
1181                    todelete.reverse()
1182                    for i in todelete :
1183                        del fields[k][i]
1184            if fields.get("objectClass") or fields.get("objectclass") :
1185                self.doModify(group.ident, fields, ignoreold=0)       
1186            else :   
1187                self.doDelete(group.ident)
1188               
1189    def deletePrinter(self, printer) :   
1190        """Completely deletes an user from the Quota Storage."""
1191        result = self.doSearch("(&(objectClass=pykotaLastJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["lastjobbase"])
1192        for (ident, fields) in result :
1193            self.doDelete(ident)
1194        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["jobbase"])
1195        for (ident, fields) in result :
1196            self.doDelete(ident)
1197        if self.info["groupquotabase"].lower() == "group" :
1198            base = self.info["groupbase"]
1199        else :
1200            base = self.info["groupquotabase"]
1201        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1202        for (ident, fields) in result :
1203            self.doDelete(ident)
1204        if self.info["userquotabase"].lower() == "user" :
1205            base = self.info["userbase"]
1206        else :
1207            base = self.info["userquotabase"]
1208        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1209        for (ident, fields) in result :
1210            self.doDelete(ident)
1211        for parent in self.getParentPrinters(printer) : 
1212            try :
1213                parent.uniqueMember.remove(printer.ident)
1214            except ValueError :   
1215                pass
1216            else :   
1217                fields = {
1218                           "uniqueMember" : parent.uniqueMember,
1219                         } 
1220                self.doModify(parent.ident, fields)         
1221        self.doDelete(printer.ident)   
1222       
1223    def extractPrinters(self, extractonly={}) :
1224        """Extracts all printer records."""
1225        pname = extractonly.get("printername")
1226        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1227        if entries :
1228            result = [ ("dn", "printername", "priceperpage", "priceperjob", "description") ]
1229            for entry in entries :
1230                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description))
1231            return result 
1232       
1233    def extractUsers(self, extractonly={}) :
1234        """Extracts all user records."""
1235        uname = extractonly.get("username")
1236        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1237        if entries :
1238            result = [ ("dn", "username", "balance", "lifetimepaid", "limitby", "email") ]
1239            for entry in entries :
1240                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email))
1241            return result 
1242       
1243    def extractGroups(self, extractonly={}) :
1244        """Extracts all group records."""
1245        gname = extractonly.get("groupname")
1246        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1247        if entries :
1248            result = [ ("dn", "groupname", "limitby", "balance", "lifetimepaid") ]
1249            for entry in entries :
1250                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid))
1251            return result 
1252       
1253    def extractPayments(self, extractonly={}) :
1254        """Extracts all payment records."""
1255        uname = extractonly.get("username")
1256        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1257        if entries :
1258            result = [ ("username", "amount", "date") ]
1259            for entry in entries :
1260                for (date, amount) in entry.Payments :
1261                    result.append((entry.Name, amount, date))
1262            return result       
1263       
1264    def extractUpquotas(self, extractonly={}) :
1265        """Extracts all userpquota records."""
1266        pname = extractonly.get("printername")
1267        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1268        if entries :
1269            result = [ ("username", "printername", "dn", "userdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1270            uname = extractonly.get("username")
1271            for entry in entries :
1272                for (user, userpquota) in self.getPrinterUsersAndQuotas(entry, names=[uname or "*"]) :
1273                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit))
1274            return result
1275       
1276    def extractGpquotas(self, extractonly={}) :
1277        """Extracts all grouppquota records."""
1278        pname = extractonly.get("printername")
1279        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1280        if entries :
1281            result = [ ("groupname", "printername", "dn", "groupdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1282            gname = extractonly.get("groupname")
1283            for entry in entries :
1284                for (group, grouppquota) in self.getPrinterGroupsAndQuotas(entry, names=[gname or "*"]) :
1285                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
1286            return result
1287       
1288    def extractUmembers(self, extractonly={}) :
1289        """Extracts all user groups members."""
1290        gname = extractonly.get("groupname")
1291        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1292        if entries :
1293            result = [ ("groupname", "username", "groupdn", "userdn") ]
1294            uname = extractonly.get("username")
1295            for entry in entries :
1296                for member in entry.Members :
1297                    if (uname is None) or (member.Name == uname) :
1298                        result.append((entry.Name, member.Name, entry.ident, member.ident))
1299            return result       
1300               
1301    def extractPmembers(self, extractonly={}) :
1302        """Extracts all printer groups members."""
1303        pname = extractonly.get("printername")
1304        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1305        if entries :
1306            result = [ ("pgroupname", "printername", "pgroupdn", "printerdn") ]
1307            pgname = extractonly.get("pgroupname")
1308            for entry in entries :
1309                for parent in self.getParentPrinters(entry) :
1310                    if (pgname is None) or (parent.Name == pgname) :
1311                        result.append((parent.Name, entry.Name, parent.ident, entry.ident))
1312            return result       
1313       
1314    def extractHistory(self, extractonly={}) :
1315        """Extracts all jobhistory records."""
1316        uname = extractonly.get("username")
1317        if uname :
1318            user = self.getUser(uname)
1319        else :   
1320            user = None
1321        pname = extractonly.get("printername")
1322        if pname :
1323            printer = self.getPrinter(pname)
1324        else :   
1325            printer = None
1326        entries = self.retrieveHistory(user, printer, limit=None)
1327        if entries :
1328            result = [ ("username", "printername", "dn", "jobid", "pagecounter", "jobsize", "action", "jobdate", "filename", "title", "copies", "options", "jobprice", "hostname", "jobsizebytes", "md5sum", "pages", "billingcode") ] 
1329            for entry in entries :
1330                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)) 
1331            return result   
Note: See TracBrowser for help on using the browser.