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

Revision 2679, 87.7 kB (checked in by jerome, 18 years ago)

Better UUID generator => caused clashes on fast servers

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