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

Revision 2266, 70.6 kB (checked in by jerome, 19 years ago)

Now dumpykota and dumpykota.cgi accept start= and end=
to specify the starting and ending dates when dumping the
history.
Syntax allowed is :

start|end=YYYY[MM[DD[hh[mm[ss]]]]]

and this is REALLY powerful !

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