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

Revision 3489, 98.5 kB (checked in by jerome, 15 years ago)

Removed bad copy and paste artifact.

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