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

Revision 2218, 69.7 kB (checked in by jerome, 19 years ago)

The data dumper now supports filtering by hostname
or billing code.

  • 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 = self.databaseToUserCharset(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, jobpages=None, jobbilling=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                   "pykotaPages" : jobpages,            # don't add this attribute if it is not set, so no string conversion
958                   "pykotaBillingCode" : self.userCharsetToDatabase(jobbilling), # don't add this attribute if it is not set, so no string conversion
959                 }
960        if (not self.disablehistory) or (not printer.LastJob.Exists) :
961            if jobsize is not None :         
962                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
963            self.doAdd(dn, fields)
964        else :   
965            # here we explicitly want to reset jobsize to 'None' if needed
966            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
967            self.doModify(dn, fields)
968           
969        if printer.LastJob.Exists :
970            fields = {
971                       "pykotaLastJobIdent" : uuid,
972                     }
973            self.doModify(printer.LastJob.lastjobident, fields)         
974        else :   
975            lastjuuid = self.genUUID()
976            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
977            fields = {
978                       "objectClass" : ["pykotaObject", "pykotaLastJob"],
979                       "cn" : lastjuuid,
980                       "pykotaPrinterName" : printer.Name,
981                       "pykotaLastJobIdent" : uuid,
982                     } 
983            self.doAdd(lastjdn, fields)         
984           
985    def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
986        """Sets soft and hard limits for a user quota."""
987        fields = { 
988                   "pykotaSoftLimit" : str(softlimit),
989                   "pykotaHardLimit" : str(hardlimit),
990                   "pykotaDateLimit" : "None",
991                   "pykotaWarnCount" : "0",
992                 }
993        self.doModify(userpquota.ident, fields)
994       
995    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
996        """Sets the warn counter value for a user quota."""
997        fields = { 
998                   "pykotaWarnCount" : str(warncount or 0),
999                 }
1000        self.doModify(userpquota.ident, fields)
1001       
1002    def increaseUserPQuotaWarnCount(self, userpquota) :
1003        """Increases the warn counter value for a user quota."""
1004        fields = {
1005                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
1006                 }
1007        return self.doModify(userpquota.ident, fields)         
1008       
1009    def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
1010        """Sets soft and hard limits for a group quota on a specific printer."""
1011        fields = { 
1012                   "pykotaSoftLimit" : str(softlimit),
1013                   "pykotaHardLimit" : str(hardlimit),
1014                   "pykotaDateLimit" : "None",
1015                 }
1016        self.doModify(grouppquota.ident, fields)
1017           
1018    def writePrinterToGroup(self, pgroup, printer) :
1019        """Puts a printer into a printer group."""
1020        if printer.ident not in pgroup.uniqueMember :
1021            pgroup.uniqueMember.append(printer.ident)
1022            fields = {
1023                       "uniqueMember" : pgroup.uniqueMember
1024                     } 
1025            self.doModify(pgroup.ident, fields)         
1026           
1027    def removePrinterFromGroup(self, pgroup, printer) :
1028        """Removes a printer from a printer group."""
1029        try :
1030            pgroup.uniqueMember.remove(printer.ident)
1031        except ValueError :   
1032            pass
1033        else :   
1034            fields = {
1035                       "uniqueMember" : pgroup.uniqueMember,
1036                     } 
1037            self.doModify(pgroup.ident, fields)         
1038           
1039    def retrieveHistory(self, user=None, printer=None, datelimit=None, hostname=None, billingcode=None, limit=100) :
1040        """Retrieves all print jobs for user on printer (or all) before date, limited to first 100 results."""
1041        precond = "(objectClass=pykotaJob)"
1042        where = []
1043        if (user is not None) and user.Exists :
1044            where.append("(pykotaUserName=%s)" % user.Name)
1045        if (printer is not None) and printer.Exists :
1046            where.append("(pykotaPrinterName=%s)" % printer.Name)
1047        if hostname is not None :
1048            where.append("(pykotaHostName=%s)" % hostname)
1049        if billingcode is not None :
1050            where.append("(pykotaBillingCode=%s)" % self.userCharsetToDatabase(billingcode))
1051        if where :   
1052            where = "(&%s)" % "".join([precond] + where)
1053        else :   
1054            where = precond
1055        jobs = []   
1056        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
1057                                               "pykotaHostName", 
1058                                               "pykotaUserName", 
1059                                               "pykotaPrinterName", 
1060                                               "pykotaJobId", 
1061                                               "pykotaPrinterPageCounter", 
1062                                               "pykotaAction", 
1063                                               "pykotaJobSize", 
1064                                               "pykotaJobPrice", 
1065                                               "pykotaFileName", 
1066                                               "pykotaTitle", 
1067                                               "pykotaCopies", 
1068                                               "pykotaOptions", 
1069                                               "pykotaBillingCode", 
1070                                               "pykotaPages", 
1071                                               "pykotaMD5Sum", 
1072                                               "createTimestamp" ], 
1073                                      base=self.info["jobbase"])
1074        if result :
1075            for (ident, fields) in result :
1076                job = StorageJob(self)
1077                job.ident = ident
1078                job.JobId = fields.get("pykotaJobId")[0]
1079                job.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
1080                try :
1081                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
1082                except ValueError :   
1083                    job.JobSize = None
1084                try :   
1085                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
1086                except ValueError :
1087                    job.JobPrice = None
1088                job.JobAction = fields.get("pykotaAction", [""])[0]
1089                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
1090                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
1091                job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
1092                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
1093                job.JobHostName = fields.get("pykotaHostName", [""])[0]
1094                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
1095                job.JobBillingCode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [None])[0])
1096                job.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
1097                job.JobPages = fields.get("pykotaPages", [""])[0]
1098                date = fields.get("createTimestamp", ["19700101000000"])[0]
1099                year = int(date[:4])
1100                month = int(date[4:6])
1101                day = int(date[6:8])
1102                hour = int(date[8:10])
1103                minute = int(date[10:12])
1104                second = int(date[12:14])
1105                job.JobDate = "%04i-%02i-%02i %02i:%02i:%02i" % (year, month, day, hour, minute, second)
1106                if (datelimit is None) or (job.JobDate <= datelimit) :
1107                    job.UserName = fields.get("pykotaUserName")[0]
1108                    job.PrinterName = fields.get("pykotaPrinterName")[0]
1109                    job.Exists = 1
1110                    jobs.append(job)
1111            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))       
1112            if limit :   
1113                jobs = jobs[:int(limit)]
1114        return jobs
1115       
1116    def deleteUser(self, user) :   
1117        """Completely deletes an user from the Quota Storage."""
1118        todelete = []   
1119        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % user.Name, base=self.info["jobbase"])
1120        for (ident, fields) in result :
1121            todelete.append(ident)
1122        if self.info["userquotabase"].lower() == "user" :
1123            base = self.info["userbase"]
1124        else :
1125            base = self.info["userquotabase"]
1126        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s))" % user.Name, ["pykotaPrinterName", "pykotaUserName"], base=base)
1127        for (ident, fields) in result :
1128            # ensure the user print quota entry will be deleted
1129            todelete.append(ident)
1130           
1131            # if last job of current printer was printed by the user
1132            # to delete, we also need to delete the printer's last job entry.
1133            printername = fields["pykotaPrinterName"][0]
1134            printer = self.getPrinter(printername)
1135            if printer.LastJob.UserName == user.Name :
1136                todelete.append(printer.LastJob.lastjobident)
1137           
1138        for ident in todelete :   
1139            self.doDelete(ident)
1140           
1141        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)   
1142        if result :
1143            fields = result[0][1]
1144            for k in fields.keys() :
1145                if k.startswith("pykota") :
1146                    del fields[k]
1147                elif k.lower() == "objectclass" :   
1148                    todelete = []
1149                    for i in range(len(fields[k])) :
1150                        if fields[k][i].startswith("pykota") : 
1151                            todelete.append(i)
1152                    todelete.sort()       
1153                    todelete.reverse()
1154                    for i in todelete :
1155                        del fields[k][i]
1156            if fields.get("objectClass") or fields.get("objectclass") :
1157                self.doModify(user.ident, fields, ignoreold=0)       
1158            else :   
1159                self.doDelete(user.ident)
1160        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % user.Name, ["pykotaUserName"], base=self.info["balancebase"])
1161        for (ident, fields) in result :
1162            self.doDelete(ident)
1163       
1164    def deleteGroup(self, group) :   
1165        """Completely deletes a group from the Quota Storage."""
1166        if self.info["groupquotabase"].lower() == "group" :
1167            base = self.info["groupbase"]
1168        else :
1169            base = self.info["groupquotabase"]
1170        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s))" % group.Name, ["pykotaGroupName"], base=base)
1171        for (ident, fields) in result :
1172            self.doDelete(ident)
1173        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)   
1174        if result :
1175            fields = result[0][1]
1176            for k in fields.keys() :
1177                if k.startswith("pykota") :
1178                    del fields[k]
1179                elif k.lower() == "objectclass" :   
1180                    todelete = []
1181                    for i in range(len(fields[k])) :
1182                        if fields[k][i].startswith("pykota") : 
1183                            todelete.append(i)
1184                    todelete.sort()       
1185                    todelete.reverse()
1186                    for i in todelete :
1187                        del fields[k][i]
1188            if fields.get("objectClass") or fields.get("objectclass") :
1189                self.doModify(group.ident, fields, ignoreold=0)       
1190            else :   
1191                self.doDelete(group.ident)
1192               
1193    def deletePrinter(self, printer) :   
1194        """Completely deletes an user from the Quota Storage."""
1195        result = self.doSearch("(&(objectClass=pykotaLastJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["lastjobbase"])
1196        for (ident, fields) in result :
1197            self.doDelete(ident)
1198        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaPrinterName=%s))" % printer.Name, base=self.info["jobbase"])
1199        for (ident, fields) in result :
1200            self.doDelete(ident)
1201        if self.info["groupquotabase"].lower() == "group" :
1202            base = self.info["groupbase"]
1203        else :
1204            base = self.info["groupquotabase"]
1205        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1206        for (ident, fields) in result :
1207            self.doDelete(ident)
1208        if self.info["userquotabase"].lower() == "user" :
1209            base = self.info["userbase"]
1210        else :
1211            base = self.info["userquotabase"]
1212        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % printer.Name, base=base)
1213        for (ident, fields) in result :
1214            self.doDelete(ident)
1215        for parent in self.getParentPrinters(printer) : 
1216            try :
1217                parent.uniqueMember.remove(printer.ident)
1218            except ValueError :   
1219                pass
1220            else :   
1221                fields = {
1222                           "uniqueMember" : parent.uniqueMember,
1223                         } 
1224                self.doModify(parent.ident, fields)         
1225        self.doDelete(printer.ident)   
1226       
1227    def extractPrinters(self, extractonly={}) :
1228        """Extracts all printer records."""
1229        pname = extractonly.get("printername")
1230        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1231        if entries :
1232            result = [ ("dn", "printername", "priceperpage", "priceperjob", "description") ]
1233            for entry in entries :
1234                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description))
1235            return result 
1236       
1237    def extractUsers(self, extractonly={}) :
1238        """Extracts all user records."""
1239        uname = extractonly.get("username")
1240        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1241        if entries :
1242            result = [ ("dn", "username", "balance", "lifetimepaid", "limitby", "email") ]
1243            for entry in entries :
1244                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email))
1245            return result 
1246       
1247    def extractGroups(self, extractonly={}) :
1248        """Extracts all group records."""
1249        gname = extractonly.get("groupname")
1250        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1251        if entries :
1252            result = [ ("dn", "groupname", "limitby", "balance", "lifetimepaid") ]
1253            for entry in entries :
1254                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid))
1255            return result 
1256       
1257    def extractPayments(self, extractonly={}) :
1258        """Extracts all payment records."""
1259        uname = extractonly.get("username")
1260        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
1261        if entries :
1262            result = [ ("username", "amount", "date") ]
1263            for entry in entries :
1264                for (date, amount) in entry.Payments :
1265                    result.append((entry.Name, amount, date))
1266            return result       
1267       
1268    def extractUpquotas(self, extractonly={}) :
1269        """Extracts all userpquota records."""
1270        pname = extractonly.get("printername")
1271        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1272        if entries :
1273            result = [ ("username", "printername", "dn", "userdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1274            uname = extractonly.get("username")
1275            for entry in entries :
1276                for (user, userpquota) in self.getPrinterUsersAndQuotas(entry, names=[uname or "*"]) :
1277                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit))
1278            return result
1279       
1280    def extractGpquotas(self, extractonly={}) :
1281        """Extracts all grouppquota records."""
1282        pname = extractonly.get("printername")
1283        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1284        if entries :
1285            result = [ ("groupname", "printername", "dn", "groupdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit") ]
1286            gname = extractonly.get("groupname")
1287            for entry in entries :
1288                for (group, grouppquota) in self.getPrinterGroupsAndQuotas(entry, names=[gname or "*"]) :
1289                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
1290            return result
1291       
1292    def extractUmembers(self, extractonly={}) :
1293        """Extracts all user groups members."""
1294        gname = extractonly.get("groupname")
1295        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
1296        if entries :
1297            result = [ ("groupname", "username", "groupdn", "userdn") ]
1298            uname = extractonly.get("username")
1299            for entry in entries :
1300                for member in entry.Members :
1301                    if (uname is None) or (member.Name == uname) :
1302                        result.append((entry.Name, member.Name, entry.ident, member.ident))
1303            return result       
1304               
1305    def extractPmembers(self, extractonly={}) :
1306        """Extracts all printer groups members."""
1307        pname = extractonly.get("printername")
1308        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
1309        if entries :
1310            result = [ ("pgroupname", "printername", "pgroupdn", "printerdn") ]
1311            pgname = extractonly.get("pgroupname")
1312            for entry in entries :
1313                for parent in self.getParentPrinters(entry) :
1314                    if (pgname is None) or (parent.Name == pgname) :
1315                        result.append((parent.Name, entry.Name, parent.ident, entry.ident))
1316            return result       
1317       
1318    def extractHistory(self, extractonly={}) :
1319        """Extracts all jobhistory records."""
1320        uname = extractonly.get("username")
1321        if uname :
1322            user = self.getUser(uname)
1323        else :   
1324            user = None
1325        pname = extractonly.get("printername")
1326        if pname :
1327            printer = self.getPrinter(pname)
1328        else :   
1329            printer = None
1330        entries = self.retrieveHistory(user, printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), limit=None)
1331        if entries :
1332            result = [ ("username", "printername", "dn", "jobid", "pagecounter", "jobsize", "action", "jobdate", "filename", "title", "copies", "options", "jobprice", "hostname", "jobsizebytes", "md5sum", "pages", "billingcode") ] 
1333            for entry in entries :
1334                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)) 
1335            return result   
Note: See TracBrowser for help on using the browser.