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

Revision 3555, 98.1 kB (checked in by jerome, 12 years ago)

Strange bug in payment's content.

  • 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)
[3555]403                        try :
404                            (date, amount) = payment.split(" # ")
405                        except ValueError :
406                            # Payment with no date ? Bug or something else ?
407                            amount = payment.split(" # ")[0]
408                            date = ""
[2452]409                        description = ""
[3413]410                    else :
[3294]411                        description = databaseToUnicode(base64.decodestring(description))
[3413]412                    if amount.endswith(" #") :
[3244]413                        amount = amount[:-2] # TODO : should be catched earlier, the bug is above I think
[2452]414                    user.Payments.append((date, float(amount), description))
[2950]415            user.Exists = True
[1041]416        return user
[3413]417
418    def getGroupFromBackend(self, groupname) :
[1041]419        """Extracts group information given its name."""
420        group = StorageGroup(self, groupname)
[3294]421        groupname = unicodeToDatabase(groupname)
[2721]422        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), ["pykotaGroupName", "pykotaLimitBy", "description"], base=self.info["groupbase"])
[1030]423        if result :
[1041]424            fields = result[0][1]
425            group.ident = result[0][0]
[3364]426            group.Name = databaseToUnicode(fields.get("pykotaGroupName", [groupname])[0])
[3294]427            group.Description = databaseToUnicode(fields.get("description", [None])[0])
[3363]428            group.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[1041]429            group.AccountBalance = 0.0
430            group.LifeTimePaid = 0.0
[1137]431            for member in self.getGroupMembers(group) :
[1075]432                if member.Exists :
433                    group.AccountBalance += member.AccountBalance
434                    group.LifeTimePaid += member.LifeTimePaid
[2950]435            group.Exists = True
[1041]436        return group
[3413]437
438    def getPrinterFromBackend(self, printername) :
[1451]439        """Extracts printer information given its name : returns first matching printer."""
[1041]440        printer = StoragePrinter(self, printername)
[3294]441        printername = unicodeToDatabase(printername)
[2459]442        result = self.doSearch("(&(objectClass=pykotaPrinter)(|(pykotaPrinterName=%s)(%s=%s)))" \
443                      % (printername, self.info["printerrdn"], printername), \
444                        ["pykotaPrinterName", "pykotaPricePerPage", \
445                         "pykotaPricePerJob", "pykotaMaxJobSize", \
446                         "pykotaPassThrough", "uniqueMember", "description"], \
447                      base=self.info["printerbase"])
[1016]448        if result :
[1451]449            fields = result[0][1]       # take only first matching printer, ignore the rest
[1041]450            printer.ident = result[0][0]
[3364]451            printer.Name = databaseToUnicode(fields.get("pykotaPrinterName", [printername])[0])
[2054]452            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0])
453            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0])
[3549]454            printer.MaxJobSize = fields.get("pykotaMaxJobSize")
455            if printer.MaxJobSize is not None :
456                if printer.MaxJobSize[0].upper() == "NONE" :
457                    printer.MaxJobSize = None
458                else :
459                    printer.MaxJobSize = int(printer.MaxJobSize[0])
[2459]460            printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
461            if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
462                printer.PassThrough = 1
463            else :
464                printer.PassThrough = 0
[1258]465            printer.uniqueMember = fields.get("uniqueMember", [])
[3413]466            printer.Description = databaseToUnicode(fields.get("description", [""])[0])
[2950]467            printer.Exists = True
[3413]468        return printer
469
470    def getUserPQuotaFromBackend(self, user, printer) :
[1041]471        """Extracts a user print quota."""
472        userpquota = StorageUserPQuota(self, user, printer)
[1228]473        if printer.Exists and user.Exists :
[1969]474            if self.info["userquotabase"].lower() == "user" :
[1998]475                base = user.ident
[3413]476            else :
[1998]477                base = self.info["userquotabase"]
[2652]478            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % \
[3294]479                                      (unicodeToDatabase(user.Name), unicodeToDatabase(printer.Name)), \
[2749]480                                      ["pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount", "pykotaMaxJobSize"], \
[2652]481                                      base=base)
[1017]482            if result :
[1041]483                fields = result[0][1]
484                userpquota.ident = result[0][0]
[2054]485                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
486                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
487                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
[1041]488                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
489                if userpquota.SoftLimit is not None :
490                    if userpquota.SoftLimit[0].upper() == "NONE" :
491                        userpquota.SoftLimit = None
[3413]492                    else :
[1041]493                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
494                userpquota.HardLimit = fields.get("pykotaHardLimit")
495                if userpquota.HardLimit is not None :
496                    if userpquota.HardLimit[0].upper() == "NONE" :
497                        userpquota.HardLimit = None
[3413]498                    elif userpquota.HardLimit is not None :
[1041]499                        userpquota.HardLimit = int(userpquota.HardLimit[0])
500                userpquota.DateLimit = fields.get("pykotaDateLimit")
501                if userpquota.DateLimit is not None :
[3413]502                    if userpquota.DateLimit[0].upper() == "NONE" :
[1041]503                        userpquota.DateLimit = None
[3413]504                    else :
[1041]505                        userpquota.DateLimit = userpquota.DateLimit[0]
[2749]506                userpquota.MaxJobSize = fields.get("pykotaMaxJobSize")
507                if userpquota.MaxJobSize is not None :
508                    if userpquota.MaxJobSize[0].upper() == "NONE" :
509                        userpquota.MaxJobSize = None
[3413]510                    else :
[2749]511                        userpquota.MaxJobSize = int(userpquota.MaxJobSize[0])
[2950]512                userpquota.Exists = True
[1041]513        return userpquota
[3413]514
515    def getGroupPQuotaFromBackend(self, group, printer) :
[1041]516        """Extracts a group print quota."""
517        grouppquota = StorageGroupPQuota(self, group, printer)
518        if group.Exists :
[1969]519            if self.info["groupquotabase"].lower() == "group" :
[1998]520                base = group.ident
[3413]521            else :
[1998]522                base = self.info["groupquotabase"]
[2652]523            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % \
[3294]524                                      (unicodeToDatabase(group.Name), unicodeToDatabase(printer.Name)), \
[3549]525                                      ["pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], \
[2652]526                                      base=base)
[1041]527            if result :
528                fields = result[0][1]
529                grouppquota.ident = result[0][0]
530                grouppquota.SoftLimit = fields.get("pykotaSoftLimit")
531                if grouppquota.SoftLimit is not None :
532                    if grouppquota.SoftLimit[0].upper() == "NONE" :
533                        grouppquota.SoftLimit = None
[3413]534                    else :
[1041]535                        grouppquota.SoftLimit = int(grouppquota.SoftLimit[0])
536                grouppquota.HardLimit = fields.get("pykotaHardLimit")
537                if grouppquota.HardLimit is not None :
538                    if grouppquota.HardLimit[0].upper() == "NONE" :
539                        grouppquota.HardLimit = None
[3413]540                    else :
[1041]541                        grouppquota.HardLimit = int(grouppquota.HardLimit[0])
542                grouppquota.DateLimit = fields.get("pykotaDateLimit")
543                if grouppquota.DateLimit is not None :
[3413]544                    if grouppquota.DateLimit[0].upper() == "NONE" :
[1041]545                        grouppquota.DateLimit = None
[3413]546                    else :
[1041]547                        grouppquota.DateLimit = grouppquota.DateLimit[0]
548                grouppquota.PageCounter = 0
549                grouppquota.LifePageCounter = 0
[3294]550                usernamesfilter = "".join(["(pykotaUserName=%s)" % unicodeToDatabase(member.Name) for member in self.getGroupMembers(group)])
[1361]551                if usernamesfilter :
552                    usernamesfilter = "(|%s)" % usernamesfilter
[1998]553                if self.info["userquotabase"].lower() == "user" :
554                    base = self.info["userbase"]
555                else :
556                    base = self.info["userquotabase"]
[2652]557                result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)%s)" % \
[3294]558                                          (unicodeToDatabase(printer.Name), usernamesfilter), \
[2652]559                                          ["pykotaPageCounter", "pykotaLifePageCounter"], base=base)
[1041]560                if result :
[3413]561                    for userpquota in result :
[1392]562                        grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0)
563                        grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0)
[2950]564                grouppquota.Exists = True
[1041]565        return grouppquota
[3413]566
567    def getPrinterLastJobFromBackend(self, printer) :
[1041]568        """Extracts a printer's last job information."""
569        lastjob = StorageLastJob(self, printer)
[3294]570        pname = unicodeToDatabase(printer.Name)
[2652]571        result = self.doSearch("(&(objectClass=pykotaLastjob)(|(pykotaPrinterName=%s)(%s=%s)))" % \
572                                  (pname, self.info["printerrdn"], pname), \
573                                  ["pykotaLastJobIdent"], \
574                                  base=self.info["lastjobbase"])
[1016]575        if result :
[1041]576            lastjob.lastjobident = result[0][0]
577            lastjobident = result[0][1]["pykotaLastJobIdent"][0]
[1692]578            result = None
579            try :
[3413]580                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes",
581                                                                  "pykotaHostName",
582                                                                  "pykotaUserName",
583                                                                  "pykotaPrinterName",
584                                                                  "pykotaJobId",
585                                                                  "pykotaPrinterPageCounter",
586                                                                  "pykotaJobSize",
587                                                                  "pykotaAction",
588                                                                  "pykotaJobPrice",
589                                                                  "pykotaFileName",
590                                                                  "pykotaTitle",
591                                                                  "pykotaCopies",
592                                                                  "pykotaOptions",
593                                                                  "pykotaBillingCode",
594                                                                  "pykotaPages",
595                                                                  "pykotaMD5Sum",
[2455]596                                                                  "pykotaPrecomputedJobSize",
597                                                                  "pykotaPrecomputedJobPrice",
[3413]598                                                                  "createTimestamp" ],
[2211]599                                                                base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
[3413]600            except PyKotaStorageError :
601                pass # Last job entry exists, but job probably doesn't exist anymore.
[1017]602            if result :
[1041]603                fields = result[0][1]
604                lastjob.ident = result[0][0]
[3364]605                lastjob.JobId = databaseToUnicode(fields.get("pykotaJobId")[0])
[3294]606                lastjob.UserName = databaseToUnicode(fields.get("pykotaUserName")[0])
[2054]607                lastjob.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0])
[1601]608                try :
609                    lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0])
[3413]610                except ValueError :
[1601]611                    lastjob.JobSize = None
[3413]612                try :
[1601]613                    lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
[3413]614                except ValueError :
[1601]615                    lastjob.JobPrice = None
[3364]616                lastjob.JobAction = databaseToUnicode(fields.get("pykotaAction", [""])[0])
[3413]617                lastjob.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0])
618                lastjob.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0])
[1203]619                lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0])
[3413]620                lastjob.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0])
[3364]621                lastjob.JobHostName = databaseToUnicode(fields.get("pykotaHostName", [""])[0])
[1520]622                lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
[3294]623                lastjob.JobBillingCode = databaseToUnicode(fields.get("pykotaBillingCode", [None])[0])
[3364]624                lastjob.JobMD5Sum = databaseToUnicode(fields.get("pykotaMD5Sum", [None])[0])
[2054]625                lastjob.JobPages = fields.get("pykotaPages", [""])[0]
[2455]626                try :
627                    lastjob.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
[3413]628                except ValueError :
[2455]629                    lastjob.PrecomputedJobSize = None
[3413]630                try :
[2455]631                    lastjob.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
[3413]632                except ValueError :
[2455]633                    lastjob.PrecomputedJobPrice = None
[3364]634                if lastjob.JobTitle == lastjob.JobFileName == lastjob.JobOptions == u"hidden" :
[2287]635                    (lastjob.JobTitle, lastjob.JobFileName, lastjob.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
[2538]636                date = fields.get("createTimestamp", ["19700101000000Z"])[0] # It's in UTC !
637                mxtime = DateTime.strptime(date[:14], "%Y%m%d%H%M%S").localtime()
[2953]638                lastjob.JobDate = mxtime.strftime("%Y-%m-%d %H:%M:%S")
[2950]639                lastjob.Exists = True
[1041]640        return lastjob
[3413]641
642    def getGroupMembersFromBackend(self, group) :
[1137]643        """Returns the group's members list."""
644        groupmembers = []
[3294]645        gname = unicodeToDatabase(group.Name)
[2652]646        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % \
647                                  (gname, self.info["grouprdn"], gname), \
648                                  [self.info["groupmembers"]], \
649                                  base=self.info["groupbase"])
[1137]650        if result :
651            for username in result[0][1].get(self.info["groupmembers"], []) :
[3294]652                groupmembers.append(self.getUser(databaseToUnicode(username)))
[3413]653        return groupmembers
654
655    def getUserGroupsFromBackend(self, user) :
[1130]656        """Returns the user's groups list."""
657        groups = []
[3294]658        uname = unicodeToDatabase(user.Name)
[2652]659        result = self.doSearch("(&(objectClass=pykotaGroup)(%s=%s))" % \
660                                  (self.info["groupmembers"], uname), \
661                                  [self.info["grouprdn"], "pykotaGroupName", "pykotaLimitBy"], \
662                                  base=self.info["groupbase"])
[1130]663        if result :
664            for (groupid, fields) in result :
[3294]665                groupname = databaseToUnicode((fields.get("pykotaGroupName", [None]) or fields.get(self.info["grouprdn"], [None]))[0])
[1147]666                group = self.getFromCache("GROUPS", groupname)
667                if group is None :
668                    group = StorageGroup(self, groupname)
669                    group.ident = groupid
670                    group.LimitBy = fields.get("pykotaLimitBy")
671                    if group.LimitBy is not None :
[3363]672                        group.LimitBy = databaseToUnicode(group.LimitBy[0])
[3413]673                    else :
[3363]674                        group.LimitBy = u"quota"
[1147]675                    group.AccountBalance = 0.0
676                    group.LifeTimePaid = 0.0
677                    for member in self.getGroupMembers(group) :
678                        if member.Exists :
679                            group.AccountBalance += member.AccountBalance
680                            group.LifeTimePaid += member.LifeTimePaid
[2950]681                    group.Exists = True
[1147]682                    self.cacheEntry("GROUPS", group.Name, group)
683                groups.append(group)
[3413]684        return groups
685
686    def getParentPrintersFromBackend(self, printer) :
[1249]687        """Get all the printer groups this printer is a member of."""
688        pgroups = []
[2652]689        result = self.doSearch("(&(objectClass=pykotaPrinter)(uniqueMember=%s))" % \
690                                  printer.ident, \
691                                  ["pykotaPrinterName"], \
692                                  base=self.info["printerbase"])
[1249]693        if result :
694            for (printerid, fields) in result :
695                if printerid != printer.ident : # In case of integrity violation.
[3294]696                    parentprinter = self.getPrinter(databaseToUnicode(fields.get("pykotaPrinterName")[0]))
[1249]697                    if parentprinter.Exists :
698                        pgroups.append(parentprinter)
699        return pgroups
[3413]700
[1041]701    def getMatchingPrinters(self, printerpattern) :
702        """Returns the list of all printers for which name matches a certain pattern."""
703        printers = []
704        # see comment at the same place in pgstorage.py
[2657]705        result = self.doSearch("objectClass=pykotaPrinter", \
[2652]706                                  ["pykotaPrinterName", "pykotaPricePerPage", "pykotaPricePerJob", "pykotaMaxJobSize", "pykotaPassThrough", "uniqueMember", "description"], \
707                                  base=self.info["printerbase"])
[1016]708        if result :
[2657]709            patterns = printerpattern.split(",")
[3521]710            patdict = {}.fromkeys(patterns)
[1041]711            for (printerid, fields) in result :
[3294]712                printername = databaseToUnicode(fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0])
[2754]713                if patdict.has_key(printername) or self.tool.matchString(printername, patterns) :
[2657]714                    printer = StoragePrinter(self, printername)
715                    printer.ident = printerid
716                    printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
717                    printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
[3549]718                    printer.MaxJobSize = fields.get("pykotaMaxJobSize")
719                    if printer.MaxJobSize is not None :
720                        if printer.MaxJobSize[0].upper() == "NONE" :
721                            printer.MaxJobSize = None
722                        else :
723                            printer.MaxJobSize = int(printer.MaxJobSize[0])
[2657]724                    printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
725                    if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
726                        printer.PassThrough = 1
727                    else :
728                        printer.PassThrough = 0
729                    printer.uniqueMember = fields.get("uniqueMember", [])
[3413]730                    printer.Description = databaseToUnicode(fields.get("description", [""])[0])
[2950]731                    printer.Exists = True
[2657]732                    printers.append(printer)
733                    self.cacheEntry("PRINTERS", printer.Name, printer)
[3413]734        return printers
735
[2657]736    def getMatchingUsers(self, userpattern) :
737        """Returns the list of all users for which name matches a certain pattern."""
738        users = []
739        # see comment at the same place in pgstorage.py
740        result = self.doSearch("objectClass=pykotaAccount", \
[3086]741                                  ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "description"], \
[2657]742                                  base=self.info["userbase"])
743        if result :
744            patterns = userpattern.split(",")
[3521]745            patdict = {}.fromkeys(patterns)
[2657]746            for (userid, fields) in result :
[3294]747                username = databaseToUnicode(fields.get("pykotaUserName", [""])[0] or fields.get(self.info["userrdn"], [""])[0])
[2754]748                if patdict.has_key(username) or self.tool.matchString(username, patterns) :
[2657]749                    user = StorageUser(self, username)
750                    user.ident = userid
[3364]751                    user.Email = databaseToUnicode(fields.get(self.info["usermail"], [None])[0])
[3363]752                    user.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[3413]753                    user.Description = databaseToUnicode(fields.get("description", [""])[0])
[3294]754                    uname = unicodeToDatabase(username)
[2657]755                    result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \
756                                              (uname, self.info["balancerdn"], uname), \
[3086]757                                              ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments", "pykotaOverCharge"], \
[2657]758                                              base=self.info["balancebase"])
759                    if not result :
760                        raise PyKotaStorageError, _("No pykotaAccountBalance object found for user %s. Did you create LDAP entries manually ?") % username
761                    else :
762                        fields = result[0][1]
763                        user.idbalance = result[0][0]
[3086]764                        user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
[2657]765                        user.AccountBalance = fields.get("pykotaBalance")
766                        if user.AccountBalance is not None :
767                            if user.AccountBalance[0].upper() == "NONE" :
768                                user.AccountBalance = None
[3413]769                            else :
[2657]770                                user.AccountBalance = float(user.AccountBalance[0])
[3413]771                        user.AccountBalance = user.AccountBalance or 0.0
[2657]772                        user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
773                        if user.LifeTimePaid is not None :
774                            if user.LifeTimePaid[0].upper() == "NONE" :
775                                user.LifeTimePaid = None
[3413]776                            else :
[2657]777                                user.LifeTimePaid = float(user.LifeTimePaid[0])
[3413]778                        user.LifeTimePaid = user.LifeTimePaid or 0.0
[2657]779                        user.Payments = []
780                        for payment in fields.get("pykotaPayments", []) :
781                            try :
782                                (date, amount, description) = payment.split(" # ")
783                            except ValueError :
784                                # Payment with no description (old Payment)
785                                (date, amount) = payment.split(" # ")
786                                description = ""
[3413]787                            else :
[3294]788                                description = databaseToUnicode(base64.decodestring(description))
[3413]789                            if amount.endswith(" #") :
[3243]790                                amount = amount[:-2] # TODO : should be catched earlier, the bug is above I think
[2657]791                            user.Payments.append((date, float(amount), description))
[2950]792                    user.Exists = True
[2657]793                    users.append(user)
794                    self.cacheEntry("USERS", user.Name, user)
[3413]795        return users
796
[2657]797    def getMatchingGroups(self, grouppattern) :
798        """Returns the list of all groups for which name matches a certain pattern."""
799        groups = []
800        # see comment at the same place in pgstorage.py
801        result = self.doSearch("objectClass=pykotaGroup", \
[2721]802                                  ["pykotaGroupName", "pykotaLimitBy", "description"], \
[2657]803                                  base=self.info["groupbase"])
804        if result :
805            patterns = grouppattern.split(",")
[3521]806            patdict = {}.fromkeys(patterns)
[2657]807            for (groupid, fields) in result :
[3294]808                groupname = databaseToUnicode(fields.get("pykotaGroupName", [""])[0] or fields.get(self.info["grouprdn"], [""])[0])
[2754]809                if patdict.has_key(groupname) or self.tool.matchString(groupname, patterns) :
[2657]810                    group = StorageGroup(self, groupname)
811                    group.ident = groupid
[3413]812                    group.Name = databaseToUnicode(fields.get("pykotaGroupName", [groupname])[0])
[3363]813                    group.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
[3413]814                    group.Description = databaseToUnicode(fields.get("description", [""])[0])
[2657]815                    group.AccountBalance = 0.0
816                    group.LifeTimePaid = 0.0
817                    for member in self.getGroupMembers(group) :
818                        if member.Exists :
819                            group.AccountBalance += member.AccountBalance
820                            group.LifeTimePaid += member.LifeTimePaid
[2950]821                    group.Exists = True
[2752]822                    groups.append(group)
823                    self.cacheEntry("GROUPS", group.Name, group)
[2657]824        return groups
[3413]825
826    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :
[1041]827        """Returns the list of users who uses a given printer, along with their quotas."""
828        usersandquotas = []
[3294]829        pname = unicodeToDatabase(printer.Name)
830        names = [unicodeToDatabase(n) for n in names]
[1998]831        if self.info["userquotabase"].lower() == "user" :
[2830]832            base = self.info["userbase"]
[1998]833        else :
[2830]834            base = self.info["userquotabase"]
[2652]835        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)(|%s))" % \
836                                  (pname, "".join(["(pykotaUserName=%s)" % uname for uname in names])), \
837                                  ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], \
838                                  base=base)
[1041]839        if result :
840            for (userquotaid, fields) in result :
[3294]841                user = self.getUser(databaseToUnicode(fields.get("pykotaUserName")[0]))
[1133]842                userpquota = StorageUserPQuota(self, user, printer)
843                userpquota.ident = userquotaid
[2054]844                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
845                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
846                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
[1133]847                userpquota.SoftLimit = fields.get("pykotaSoftLimit")
848                if userpquota.SoftLimit is not None :
849                    if userpquota.SoftLimit[0].upper() == "NONE" :
850                        userpquota.SoftLimit = None
[3413]851                    else :
[1133]852                        userpquota.SoftLimit = int(userpquota.SoftLimit[0])
853                userpquota.HardLimit = fields.get("pykotaHardLimit")
854                if userpquota.HardLimit is not None :
855                    if userpquota.HardLimit[0].upper() == "NONE" :
856                        userpquota.HardLimit = None
[3413]857                    elif userpquota.HardLimit is not None :
[1133]858                        userpquota.HardLimit = int(userpquota.HardLimit[0])
859                userpquota.DateLimit = fields.get("pykotaDateLimit")
860                if userpquota.DateLimit is not None :
[3413]861                    if userpquota.DateLimit[0].upper() == "NONE" :
[1133]862                        userpquota.DateLimit = None
[3413]863                    else :
[1133]864                        userpquota.DateLimit = userpquota.DateLimit[0]
[2950]865                userpquota.Exists = True
[1133]866                usersandquotas.append((user, userpquota))
867                self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
[3413]868        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))
[1041]869        return usersandquotas
[3413]870
871    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :
[1041]872        """Returns the list of groups which uses a given printer, along with their quotas."""
873        groupsandquotas = []
[3294]874        pname = unicodeToDatabase(printer.Name)
875        names = [unicodeToDatabase(n) for n in names]
[1998]876        if self.info["groupquotabase"].lower() == "group" :
[2830]877            base = self.info["groupbase"]
[1998]878        else :
[2830]879            base = self.info["groupquotabase"]
[2652]880        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s)(|%s))" % \
881                                  (pname, "".join(["(pykotaGroupName=%s)" % gname for gname in names])), \
882                                  ["pykotaGroupName"], \
883                                  base=base)
[1041]884        if result :
885            for (groupquotaid, fields) in result :
[3294]886                group = self.getGroup(databaseToUnicode(fields.get("pykotaGroupName")[0]))
[1133]887                grouppquota = self.getGroupPQuota(group, printer)
888                groupsandquotas.append((group, grouppquota))
[3413]889        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))
[1041]890        return groupsandquotas
[3413]891
[2768]892    def addPrinter(self, printer) :
893        """Adds a printer to the quota storage, returns the old value if it already exists."""
894        oldentry = self.getPrinter(printer.Name)
895        if oldentry.Exists :
896            return oldentry # we return the existing entry
[3294]897        printername = unicodeToDatabase(printer.Name)
[1030]898        fields = { self.info["printerrdn"] : printername,
899                   "objectClass" : ["pykotaObject", "pykotaPrinter"],
[1041]900                   "cn" : printername,
[1030]901                   "pykotaPrinterName" : printername,
[2768]902                   "pykotaPassThrough" : (printer.PassThrough and "t") or "f",
[3549]903                   "pykotaMaxJobSize" : str(printer.MaxJobSize),
[3294]904                   "description" : unicodeToDatabase(printer.Description or ""),
[2768]905                   "pykotaPricePerPage" : str(printer.PricePerPage or 0.0),
906                   "pykotaPricePerJob" : str(printer.PricePerJob or 0.0),
[3413]907                 }
[1030]908        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
[1041]909        self.doAdd(dn, fields)
[2768]910        printer.isDirty = False
911        return None # the entry created doesn't need further modification
[3413]912
913    def addUser(self, user) :
[2773]914        """Adds a user to the quota storage, returns the old value if it already exists."""
915        oldentry = self.getUser(user.Name)
916        if oldentry.Exists :
917            return oldentry # we return the existing entry
[3294]918        uname = unicodeToDatabase(user.Name)
[1105]919        newfields = {
[2652]920                       "pykotaUserName" : uname,
[3363]921                       "pykotaLimitBy" : unicodeToDatabase(user.LimitBy or u"quota"),
[3294]922                       "description" : unicodeToDatabase(user.Description or ""),
[3364]923                       self.info["usermail"] : unicodeToDatabase(user.Email or ""),
[3413]924                    }
925
[1105]926        mustadd = 1
927        if self.info["newuser"].lower() != 'below' :
[1510]928            try :
929                (where, action) = [s.strip() for s in self.info["newuser"].split(",")]
930            except ValueError :
931                (where, action) = (self.info["newuser"].strip(), "fail")
[2652]932            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % \
933                                      (where, self.info["userrdn"], uname), \
934                                      None, \
935                                      base=self.info["userbase"])
[1105]936            if result :
937                (dn, fields) = result[0]
[2188]938                oc = fields.get("objectClass", fields.get("objectclass", []))
939                oc.extend(["pykotaAccount", "pykotaAccountBalance"])
[1105]940                fields.update(newfields)
[1742]941                fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0),
[3086]942                                "pykotaOverCharge" : str(user.OverCharge),
[3413]943                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })
[1105]944                self.doModify(dn, fields)
945                mustadd = 0
[1510]946            else :
[1534]947                message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name)
[3413]948                if action.lower() == "warn" :
[2191]949                    self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn")
[1510]950                else : # 'fail' or incorrect setting
951                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
[3413]952
[1105]953        if mustadd :
[3413]954            if self.info["userbase"] == self.info["balancebase"] :
[2652]955                fields = { self.info["userrdn"] : uname,
[1742]956                           "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
[2652]957                           "cn" : uname,
[1742]958                           "pykotaBalance" : str(user.AccountBalance or 0.0),
[3086]959                           "pykotaOverCharge" : str(user.OverCharge),
[3413]960                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),
961                         }
962            else :
[2652]963                fields = { self.info["userrdn"] : uname,
[1742]964                           "objectClass" : ["pykotaObject", "pykotaAccount"],
[2652]965                           "cn" : uname,
[3413]966                         }
967            fields.update(newfields)
[2652]968            dn = "%s=%s,%s" % (self.info["userrdn"], uname, self.info["userbase"])
[1105]969            self.doAdd(dn, fields)
[3413]970            if self.info["userbase"] != self.info["balancebase"] :
[2652]971                fields = { self.info["balancerdn"] : uname,
[1742]972                           "objectClass" : ["pykotaObject", "pykotaAccountBalance"],
[2652]973                           "cn" : uname,
[1742]974                           "pykotaBalance" : str(user.AccountBalance or 0.0),
[3086]975                           "pykotaOverCharge" : str(user.OverCharge),
[3413]976                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),
977                         }
[2652]978                dn = "%s=%s,%s" % (self.info["balancerdn"], uname, self.info["balancebase"])
[1742]979                self.doAdd(dn, fields)
[2773]980        user.idbalance = dn
981        if user.PaymentsBacklog :
982            for (value, comment) in user.PaymentsBacklog :
983                self.writeNewPayment(user, value, comment)
984            user.PaymentsBacklog = []
985        user.isDirty = False
986        return None # the entry created doesn't need further modification
[3413]987
988    def addGroup(self, group) :
[2773]989        """Adds a group to the quota storage, returns the old value if it already exists."""
990        oldentry = self.getGroup(group.Name)
991        if oldentry.Exists :
992            return oldentry # we return the existing entry
[3294]993        gname = unicodeToDatabase(group.Name)
[3413]994        newfields = {
[2652]995                      "pykotaGroupName" : gname,
[3363]996                      "pykotaLimitBy" : unicodeToDatabase(group.LimitBy or u"quota"),
[3294]997                      "description" : unicodeToDatabase(group.Description or "")
[3413]998                    }
[1105]999        mustadd = 1
1000        if self.info["newgroup"].lower() != 'below' :
[1510]1001            try :
1002                (where, action) = [s.strip() for s in self.info["newgroup"].split(",")]
1003            except ValueError :
1004                (where, action) = (self.info["newgroup"].strip(), "fail")
[2652]1005            result = self.doSearch("(&(objectClass=%s)(%s=%s))" % \
1006                                      (where, self.info["grouprdn"], gname), \
1007                                      None, \
1008                                      base=self.info["groupbase"])
[1105]1009            if result :
1010                (dn, fields) = result[0]
[2188]1011                oc = fields.get("objectClass", fields.get("objectclass", []))
1012                oc.extend(["pykotaGroup"])
[1105]1013                fields.update(newfields)
1014                self.doModify(dn, fields)
1015                mustadd = 0
[1510]1016            else :
1017                message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name
[3413]1018                if action.lower() == "warn" :
[1584]1019                    self.tool.printInfo("%s. A new entry will be created instead." % message, "warn")
[1510]1020                else : # 'fail' or incorrect setting
1021                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
[3413]1022
[1105]1023        if mustadd :
[2652]1024            fields = { self.info["grouprdn"] : gname,
[1105]1025                       "objectClass" : ["pykotaObject", "pykotaGroup"],
[2652]1026                       "cn" : gname,
[3413]1027                     }
1028            fields.update(newfields)
[2652]1029            dn = "%s=%s,%s" % (self.info["grouprdn"], gname, self.info["groupbase"])
[1105]1030            self.doAdd(dn, fields)
[2773]1031        group.isDirty = False
1032        return None # the entry created doesn't need further modification
[3413]1033
1034    def addUserToGroup(self, user, group) :
[1041]1035        """Adds an user to a group."""
[1141]1036        if user.Name not in [u.Name for u in self.getGroupMembers(group)] :
[3413]1037            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)
[1041]1038            if result :
1039                fields = result[0][1]
[1070]1040                if not fields.has_key(self.info["groupmembers"]) :
1041                    fields[self.info["groupmembers"]] = []
[3294]1042                fields[self.info["groupmembers"]].append(unicodeToDatabase(user.Name))
[1041]1043                self.doModify(group.ident, fields)
1044                group.Members.append(user)
[3413]1045
1046    def delUserFromGroup(self, user, group) :
[2706]1047        """Removes an user from a group."""
[2753]1048        if user.Name in [u.Name for u in self.getGroupMembers(group)] :
[2750]1049            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)
1050            if result :
1051                fields = result[0][1]
1052                if not fields.has_key(self.info["groupmembers"]) :
1053                    fields[self.info["groupmembers"]] = []
[3413]1054                try :
[3294]1055                    fields[self.info["groupmembers"]].remove(unicodeToDatabase(user.Name))
[2750]1056                except ValueError :
1057                    pass # TODO : Strange, shouldn't it be there ?
1058                else :
1059                    self.doModify(group.ident, fields)
1060                    group.Members.remove(user)
[3413]1061
[2749]1062    def addUserPQuota(self, upq) :
[1041]1063        """Initializes a user print quota on a printer."""
[2749]1064        # first check if an entry already exists
1065        oldentry = self.getUserPQuota(upq.User, upq.Printer)
1066        if oldentry.Exists :
1067            return oldentry # we return the existing entry
[1030]1068        uuid = self.genUUID()
[3294]1069        uname = unicodeToDatabase(upq.User.Name)
1070        pname = unicodeToDatabase(upq.Printer.Name)
[1041]1071        fields = { "cn" : uuid,
1072                   "objectClass" : ["pykotaObject", "pykotaUserPQuota"],
[2652]1073                   "pykotaUserName" : uname,
1074                   "pykotaPrinterName" : pname,
[2749]1075                   "pykotaSoftLimit" : str(upq.SoftLimit),
1076                   "pykotaHardLimit" : str(upq.HardLimit),
1077                   "pykotaDateLimit" : str(upq.DateLimit),
1078                   "pykotaPageCounter" : str(upq.PageCounter or 0),
1079                   "pykotaLifePageCounter" : str(upq.LifePageCounter or 0),
1080                   "pykotaWarnCount" : str(upq.WarnCount or 0),
[3549]1081                   "pykotaMaxJobSize" : str(upq.MaxJobSize),
[3413]1082                 }
[1969]1083        if self.info["userquotabase"].lower() == "user" :
[2749]1084            dn = "cn=%s,%s" % (uuid, upq.User.ident)
[3413]1085        else :
[1969]1086            dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
[1031]1087        self.doAdd(dn, fields)
[2749]1088        upq.isDirty = False
1089        return None # the entry created doesn't need further modification
[3413]1090
[2749]1091    def addGroupPQuota(self, gpq) :
[1041]1092        """Initializes a group print quota on a printer."""
[2749]1093        oldentry = self.getGroupPQuota(gpq.Group, gpq.Printer)
1094        if oldentry.Exists :
1095            return oldentry # we return the existing entry
[1030]1096        uuid = self.genUUID()
[3294]1097        gname = unicodeToDatabase(gpq.Group.Name)
1098        pname = unicodeToDatabase(gpq.Printer.Name)
[1041]1099        fields = { "cn" : uuid,
1100                   "objectClass" : ["pykotaObject", "pykotaGroupPQuota"],
[2652]1101                   "pykotaGroupName" : gname,
1102                   "pykotaPrinterName" : pname,
[1030]1103                   "pykotaDateLimit" : "None",
[3413]1104                 }
[1969]1105        if self.info["groupquotabase"].lower() == "group" :
[2749]1106            dn = "cn=%s,%s" % (uuid, gpq.Group.ident)
[3413]1107        else :
[1969]1108            dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
[1031]1109        self.doAdd(dn, fields)
[2749]1110        gpq.isDirty = False
1111        return None # the entry created doesn't need further modification
[3413]1112
1113    def savePrinter(self, printer) :
[2686]1114        """Saves the printer to the database in a single operation."""
[1041]1115        fields = {
[2686]1116                   "pykotaPassThrough" : (printer.PassThrough and "t") or "f",
[3549]1117                   "pykotaMaxJobSize" : str(printer.MaxJobSize),
[3294]1118                   "description" : unicodeToDatabase(printer.Description or ""),
[2768]1119                   "pykotaPricePerPage" : str(printer.PricePerPage or 0.0),
1120                   "pykotaPricePerJob" : str(printer.PricePerJob or 0.0),
[1041]1121                 }
1122        self.doModify(printer.ident, fields)
[3413]1123
[2706]1124    def saveUser(self, user) :
1125        """Saves the user to the database in a single operation."""
1126        newfields = {
[3363]1127                       "pykotaLimitBy" : unicodeToDatabase(user.LimitBy or u"quota"),
[3413]1128                       "description" : unicodeToDatabase(user.Description or ""),
[2938]1129                       self.info["usermail"] : user.Email or "",
[3413]1130                    }
[2706]1131        self.doModify(user.ident, newfields)
[3413]1132
[2707]1133        newfields = { "pykotaBalance" : str(user.AccountBalance or 0.0),
[3413]1134                      "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),
[3086]1135                      "pykotaOverCharge" : str(user.OverCharge),
[2707]1136                    }
1137        self.doModify(user.idbalance, newfields)
[3413]1138
[2706]1139    def saveGroup(self, group) :
1140        """Saves the group to the database in a single operation."""
1141        newfields = {
[3363]1142                       "pykotaLimitBy" : unicodeToDatabase(group.LimitBy or u"quota"),
[3413]1143                       "description" : unicodeToDatabase(group.Description or ""),
1144                    }
[2706]1145        self.doModify(group.ident, newfields)
[3413]1146
1147    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :
[1041]1148        """Sets the date limit permanently for a user print quota."""
[1031]1149        fields = {
[2880]1150                   "pykotaDateLimit" : str(datelimit),
[1031]1151                 }
[1041]1152        return self.doModify(userpquota.ident, fields)
[3413]1153
1154    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :
[1041]1155        """Sets the date limit permanently for a group print quota."""
[1031]1156        fields = {
[2880]1157                   "pykotaDateLimit" : str(datelimit),
[1031]1158                 }
[1041]1159        return self.doModify(grouppquota.ident, fields)
[3413]1160
1161    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :
[1269]1162        """Increase page counters for a user print quota."""
1163        fields = {
1164                   "pykotaPageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
1165                   "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
1166                 }
[3413]1167        return self.doModify(userpquota.ident, fields)
1168
1169    def decreaseUserAccountBalance(self, user, amount) :
[1269]1170        """Decreases user's account balance from an amount."""
1171        fields = {
1172                   "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float },
1173                 }
[3413]1174        return self.doModify(user.idbalance, fields, flushcache=1)
1175
[2452]1176    def writeNewPayment(self, user, amount, comment="") :
[1522]1177        """Adds a new payment to the payments history."""
1178        payments = []
1179        for payment in user.Payments :
[3294]1180            payments.append("%s # %s # %s" % (payment[0], str(payment[1]), base64.encodestring(unicodeToDatabase(payment[2])).strip()))
1181        payments.append("%s # %s # %s" % (str(DateTime.now()), str(amount), base64.encodestring(unicodeToDatabase(comment)).strip()))
[1522]1182        fields = {
1183                   "pykotaPayments" : payments,
1184                 }
[3413]1185        return self.doModify(user.idbalance, fields)
1186
1187    def writeLastJobSize(self, lastjob, jobsize, jobprice) :
[1041]1188        """Sets the last job's size permanently."""
1189        fields = {
1190                   "pykotaJobSize" : str(jobsize),
[1203]1191                   "pykotaJobPrice" : str(jobprice),
[1041]1192                 }
[3413]1193        self.doModify(lastjob.ident, fields)
1194
[2455]1195    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]1196        """Adds a job in a printer's history."""
[3294]1197        uname = unicodeToDatabase(user.Name)
1198        pname = unicodeToDatabase(printer.Name)
[1149]1199        if (not self.disablehistory) or (not printer.LastJob.Exists) :
1200            uuid = self.genUUID()
1201            dn = "cn=%s,%s" % (uuid, self.info["jobbase"])
[3413]1202        else :
[1149]1203            uuid = printer.LastJob.ident[3:].split(",")[0]
1204            dn = printer.LastJob.ident
[3413]1205        if self.privacy :
[1875]1206            # For legal reasons, we want to hide the title, filename and options
[3364]1207            title = filename = options = u"hidden"
[1032]1208        fields = {
1209                   "objectClass" : ["pykotaObject", "pykotaJob"],
1210                   "cn" : uuid,
[2652]1211                   "pykotaUserName" : uname,
1212                   "pykotaPrinterName" : pname,
[3364]1213                   "pykotaJobId" : unicodeToDatabase(jobid),
[1032]1214                   "pykotaPrinterPageCounter" : str(pagecounter),
[3364]1215                   "pykotaAction" : unicodeToDatabase(action),
[3413]1216                   "pykotaFileName" : ((filename is None) and "None") or unicodeToDatabase(filename),
1217                   "pykotaTitle" : ((title is None) and "None") or unicodeToDatabase(title),
1218                   "pykotaCopies" : str(copies),
1219                   "pykotaOptions" : ((options is None) and "None") or unicodeToDatabase(options),
[3532]1220                   "pykotaHostName" : unicodeToDatabase(clienthost),
[1520]1221                   "pykotaJobSizeBytes" : str(jobsizebytes),
[3364]1222                   "pykotaMD5Sum" : unicodeToDatabase(jobmd5sum),
[2217]1223                   "pykotaPages" : jobpages,            # don't add this attribute if it is not set, so no string conversion
[3294]1224                   "pykotaBillingCode" : unicodeToDatabase(jobbilling), # don't add this attribute if it is not set, so no string conversion
[2455]1225                   "pykotaPrecomputedJobSize" : str(precomputedsize),
[2456]1226                   "pykotaPrecomputedJobPrice" : str(precomputedprice),
[1032]1227                 }
[1149]1228        if (not self.disablehistory) or (not printer.LastJob.Exists) :
[3413]1229            if jobsize is not None :
[1203]1230                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
[1149]1231            self.doAdd(dn, fields)
[3413]1232        else :
[1149]1233            # here we explicitly want to reset jobsize to 'None' if needed
[1203]1234            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
[1149]1235            self.doModify(dn, fields)
[3413]1236
[1041]1237        if printer.LastJob.Exists :
[1032]1238            fields = {
1239                       "pykotaLastJobIdent" : uuid,
1240                     }
[3413]1241            self.doModify(printer.LastJob.lastjobident, fields)
1242        else :
[1032]1243            lastjuuid = self.genUUID()
[1067]1244            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
[1032]1245            fields = {
1246                       "objectClass" : ["pykotaObject", "pykotaLastJob"],
1247                       "cn" : lastjuuid,
[2652]1248                       "pykotaPrinterName" : pname,
[1032]1249                       "pykotaLastJobIdent" : uuid,
[3413]1250                     }
1251            self.doAdd(lastjdn, fields)
1252
[2735]1253    def saveUserPQuota(self, userpquota) :
1254        """Saves an user print quota entry."""
[3413]1255        fields = {
[2735]1256                   "pykotaSoftLimit" : str(userpquota.SoftLimit),
1257                   "pykotaHardLimit" : str(userpquota.HardLimit),
1258                   "pykotaDateLimit" : str(userpquota.DateLimit),
[2749]1259                   "pykotaWarnCount" : str(userpquota.WarnCount or 0),
1260                   "pykotaPageCounter" : str(userpquota.PageCounter or 0),
1261                   "pykotaLifePageCounter" : str(userpquota.LifePageCounter or 0),
[3549]1262                   "pykotaMaxJobSize" : str(userpquota.MaxJobSize),
[1041]1263                 }
1264        self.doModify(userpquota.ident, fields)
[3413]1265
[2054]1266    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
1267        """Sets the warn counter value for a user quota."""
[3413]1268        fields = {
[2054]1269                   "pykotaWarnCount" : str(warncount or 0),
1270                 }
1271        self.doModify(userpquota.ident, fields)
[3413]1272
[2054]1273    def increaseUserPQuotaWarnCount(self, userpquota) :
1274        """Increases the warn counter value for a user quota."""
1275        fields = {
1276                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
1277                 }
[3413]1278        return self.doModify(userpquota.ident, fields)
1279
[2735]1280    def saveGroupPQuota(self, grouppquota) :
1281        """Saves a group print quota entry."""
[3413]1282        fields = {
[2735]1283                   "pykotaSoftLimit" : str(grouppquota.SoftLimit),
1284                   "pykotaHardLimit" : str(grouppquota.HardLimit),
1285                   "pykotaDateLimit" : str(grouppquota.DateLimit),
[1041]1286                 }
1287        self.doModify(grouppquota.ident, fields)
[3413]1288
[1258]1289    def writePrinterToGroup(self, pgroup, printer) :
1290        """Puts a printer into a printer group."""
[1259]1291        if printer.ident not in pgroup.uniqueMember :
[1269]1292            pgroup.uniqueMember.append(printer.ident)
[1258]1293            fields = {
[1269]1294                       "uniqueMember" : pgroup.uniqueMember
[3413]1295                     }
1296            self.doModify(pgroup.ident, fields)
1297
[1332]1298    def removePrinterFromGroup(self, pgroup, printer) :
1299        """Removes a printer from a printer group."""
1300        try :
1301            pgroup.uniqueMember.remove(printer.ident)
[3413]1302        except ValueError :
[1332]1303            pass
[3413]1304        else :
[1332]1305            fields = {
1306                       "uniqueMember" : pgroup.uniqueMember,
[3413]1307                     }
1308            self.doModify(pgroup.ident, fields)
1309
[3056]1310    def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) :
[2266]1311        """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results."""
[1274]1312        precond = "(objectClass=pykotaJob)"
1313        where = []
[2222]1314        if user is not None :
[3294]1315            where.append("(pykotaUserName=%s)" % unicodeToDatabase(user.Name))
[2222]1316        if printer is not None :
[3294]1317            where.append("(pykotaPrinterName=%s)" % unicodeToDatabase(printer.Name))
[1502]1318        if hostname is not None :
[3532]1319            where.append("(pykotaHostName=%s)" % unicodeToDatabase(hostname))
[2218]1320        if billingcode is not None :
[3294]1321            where.append("(pykotaBillingCode=%s)" % unicodeToDatabase(billingcode))
[3056]1322        if jobid is not None :
[3294]1323            where.append("(pykotaJobId=%s)" % jobid) # TODO : jobid is text, so unicodeToDatabase(jobid) but do all of them as well.
[3413]1324        if where :
[1274]1325            where = "(&%s)" % "".join([precond] + where)
[3413]1326        else :
[1274]1327            where = precond
[3413]1328        jobs = []
1329        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes",
1330                                               "pykotaHostName",
1331                                               "pykotaUserName",
1332                                               "pykotaPrinterName",
1333                                               "pykotaJobId",
1334                                               "pykotaPrinterPageCounter",
1335                                               "pykotaAction",
1336                                               "pykotaJobSize",
1337                                               "pykotaJobPrice",
1338                                               "pykotaFileName",
1339                                               "pykotaTitle",
1340                                               "pykotaCopies",
1341                                               "pykotaOptions",
1342                                               "pykotaBillingCode",
1343                                               "pykotaPages",
1344                                               "pykotaMD5Sum",
[2455]1345                                               "pykotaPrecomputedJobSize",
1346                                               "pykotaPrecomputedJobPrice",
[3413]1347                                               "createTimestamp" ],
[2211]1348                                      base=self.info["jobbase"])
[1274]1349        if result :
1350            for (ident, fields) in result :
1351                job = StorageJob(self)
1352                job.ident = ident
[3364]1353                job.JobId = databaseToUnicode(fields.get("pykotaJobId")[0])
[1392]1354                job.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
[1601]1355                try :
1356                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
[3413]1357                except ValueError :
[1601]1358                    job.JobSize = None
[3413]1359                try :
[1601]1360                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
1361                except ValueError :
1362                    job.JobPrice = None
[3364]1363                job.JobAction = databaseToUnicode(fields.get("pykotaAction", [""])[0])
[3413]1364                job.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0])
1365                job.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0])
[1274]1366                job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
[3413]1367                job.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0])
[3364]1368                job.JobHostName = databaseToUnicode(fields.get("pykotaHostName", [""])[0])
[1520]1369                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
[3294]1370                job.JobBillingCode = databaseToUnicode(fields.get("pykotaBillingCode", [None])[0])
[3364]1371                job.JobMD5Sum = databaseToUnicode(fields.get("pykotaMD5Sum", [None])[0])
[2211]1372                job.JobPages = fields.get("pykotaPages", [""])[0]
[2455]1373                try :
1374                    job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
[3413]1375                except ValueError :
[2455]1376                    job.PrecomputedJobSize = None
[3413]1377                try :
[2455]1378                    job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
1379                except ValueError :
1380                    job.PrecomputedJobPrice = None
[3364]1381                if job.JobTitle == job.JobFileName == job.JobOptions == u"hidden" :
[2287]1382                    (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
[2538]1383                date = fields.get("createTimestamp", ["19700101000000Z"])[0] # It's in UTC !
1384                mxtime = DateTime.strptime(date[:14], "%Y%m%d%H%M%S").localtime()
[2953]1385                job.JobDate = mxtime.strftime("%Y-%m-%d %H:%M:%S")
[2266]1386                if ((start is None) and (end is None)) or \
1387                   ((start is None) and (job.JobDate <= end)) or \
1388                   ((end is None) and (job.JobDate >= start)) or \
1389                   ((job.JobDate >= start) and (job.JobDate <= end)) :
[3294]1390                    job.UserName = databaseToUnicode(fields.get("pykotaUserName")[0])
1391                    job.PrinterName = databaseToUnicode(fields.get("pykotaPrinterName")[0])
[2950]1392                    job.Exists = True
[1274]1393                    jobs.append(job)
[3413]1394            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))
1395            if limit :
[1274]1396                jobs = jobs[:int(limit)]
1397        return jobs
[3413]1398
1399    def deleteUser(self, user) :
[1041]1400        """Completely deletes an user from the Quota Storage."""
[3294]1401        uname = unicodeToDatabase(user.Name)
[3413]1402        todelete = []
[2652]1403        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % uname, base=self.info["jobbase"])
[1041]1404        for (ident, fields) in result :
[1692]1405            todelete.append(ident)
[1998]1406        if self.info["userquotabase"].lower() == "user" :
1407            base = self.info["userbase"]
1408        else :
1409            base = self.info["userquotabase"]
[2652]1410        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s))" % uname, \
1411                                  ["pykotaPrinterName", "pykotaUserName"], \
1412                                  base=base)
[1041]1413        for (ident, fields) in result :
[1692]1414            # ensure the user print quota entry will be deleted
1415            todelete.append(ident)
[3413]1416
[1692]1417            # if last job of current printer was printed by the user
1418            # to delete, we also need to delete the printer's last job entry.
[3294]1419            printer = self.getPrinter(databaseToUnicode(fields["pykotaPrinterName"][0]))
[1692]1420            if printer.LastJob.UserName == user.Name :
1421                todelete.append(printer.LastJob.lastjobident)
[3413]1422
1423        for ident in todelete :
[1041]1424            self.doDelete(ident)
[3413]1425
1426        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)
[1032]1427        if result :
[1041]1428            fields = result[0][1]
1429            for k in fields.keys() :
1430                if k.startswith("pykota") :
1431                    del fields[k]
[3413]1432                elif k.lower() == "objectclass" :
[1041]1433                    todelete = []
1434                    for i in range(len(fields[k])) :
[3413]1435                        if fields[k][i].startswith("pykota") :
[1041]1436                            todelete.append(i)
[3413]1437                    todelete.sort()
[1041]1438                    todelete.reverse()
1439                    for i in todelete :
1440                        del fields[k][i]
[1119]1441            if fields.get("objectClass") or fields.get("objectclass") :
[3413]1442                self.doModify(user.ident, fields, ignoreold=0)
1443            else :
[1041]1444                self.doDelete(user.ident)
[2652]1445        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % \
1446                                   uname, \
1447                                   ["pykotaUserName"], \
1448                                   base=self.info["balancebase"])
[1041]1449        for (ident, fields) in result :
1450            self.doDelete(ident)
[3413]1451
1452    def deleteGroup(self, group) :
[1041]1453        """Completely deletes a group from the Quota Storage."""
[3294]1454        gname = unicodeToDatabase(group.Name)
[1998]1455        if self.info["groupquotabase"].lower() == "group" :
1456            base = self.info["groupbase"]
1457        else :
1458            base = self.info["groupquotabase"]
[2652]1459        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s))" % \
1460                                  gname, \
1461                                  ["pykotaGroupName"], \
1462                                  base=base)
[1041]1463        for (ident, fields) in result :
1464            self.doDelete(ident)
[3413]1465        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)
[1016]1466        if result :
[1027]1467            fields = result[0][1]
[1041]1468            for k in fields.keys() :
1469                if k.startswith("pykota") :
1470                    del fields[k]
[3413]1471                elif k.lower() == "objectclass" :
[1041]1472                    todelete = []
1473                    for i in range(len(fields[k])) :
[3413]1474                        if fields[k][i].startswith("pykota") :
[1041]1475                            todelete.append(i)
[3413]1476                    todelete.sort()
[1041]1477                    todelete.reverse()
1478                    for i in todelete :
1479                        del fields[k][i]
[1119]1480            if fields.get("objectClass") or fields.get("objectclass") :
[3413]1481                self.doModify(group.ident, fields, ignoreold=0)
1482            else :
[1041]1483                self.doDelete(group.ident)
[3413]1484
[2765]1485    def deleteManyBillingCodes(self, billingcodes) :
1486        """Deletes many billing codes."""
1487        for bcode in billingcodes :
[2763]1488            bcode.delete()
[3413]1489
1490    def deleteManyUsers(self, users) :
[2765]1491        """Deletes many users."""
1492        for user in users :
[2763]1493            user.delete()
[3413]1494
1495    def deleteManyGroups(self, groups) :
[2765]1496        """Deletes many groups."""
1497        for group in groups :
[2763]1498            group.delete()
[3413]1499
1500    def deleteManyPrinters(self, printers) :
[2765]1501        """Deletes many printers."""
1502        for printer in printers :
[2763]1503            printer.delete()
[3413]1504
1505    def deleteManyUserPQuotas(self, printers, users) :
[2749]1506        """Deletes many user print quota entries."""
1507        # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !)
1508        for printer in printers :
1509            for user in users :
1510                upq = self.getUserPQuota(user, printer)
1511                if upq.Exists :
1512                    upq.delete()
[3413]1513
[2749]1514    def deleteManyGroupPQuotas(self, printers, groups) :
1515        """Deletes many group print quota entries."""
1516        # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !)
1517        for printer in printers :
1518            for group in groups :
1519                gpq = self.getGroupPQuota(group, printer)
1520                if gpq.Exists :
1521                    gpq.delete()
[3413]1522
1523    def deleteUserPQuota(self, upquota) :
[2717]1524        """Completely deletes an user print quota entry from the database."""
[3294]1525        uname = unicodeToDatabase(upquota.User.Name)
1526        pname = unicodeToDatabase(upquota.Printer.Name)
[2717]1527        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s)(pykotaPrinterName=%s))" \
1528                                   % (uname, pname), \
1529                                   base=self.info["jobbase"])
1530        for (ident, fields) in result :
1531            self.doDelete(ident)
[2718]1532        if upquota.Printer.LastJob.UserName == upquota.User.Name :
1533            self.doDelete(upquota.Printer.LastJob.lastjobident)
[2717]1534        self.doDelete(upquota.ident)
[3413]1535
1536    def deleteGroupPQuota(self, gpquota) :
[2717]1537        """Completely deletes a group print quota entry from the database."""
1538        self.doDelete(gpquota.ident)
[3413]1539
1540    def deletePrinter(self, printer) :
[2358]1541        """Completely deletes a printer from the Quota Storage."""
[3294]1542        pname = unicodeToDatabase(printer.Name)
[2652]1543        result = self.doSearch("(&(objectClass=pykotaLastJob)(pykotaPrinterName=%s))" % pname, base=self.info["lastjobbase"])
[1330]1544        for (ident, fields) in result :
1545            self.doDelete(ident)
[2652]1546        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaPrinterName=%s))" % pname, base=self.info["jobbase"])
[1330]1547        for (ident, fields) in result :
1548            self.doDelete(ident)
[1998]1549        if self.info["groupquotabase"].lower() == "group" :
1550            base = self.info["groupbase"]
1551        else :
1552            base = self.info["groupquotabase"]
[2652]1553        result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % pname, base=base)
[1330]1554        for (ident, fields) in result :
1555            self.doDelete(ident)
[1998]1556        if self.info["userquotabase"].lower() == "user" :
1557            base = self.info["userbase"]
1558        else :
1559            base = self.info["userquotabase"]
[2652]1560        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % pname, base=base)
[1330]1561        for (ident, fields) in result :
1562            self.doDelete(ident)
[3413]1563        for parent in self.getParentPrinters(printer) :
[1332]1564            try :
1565                parent.uniqueMember.remove(printer.ident)
[3413]1566            except ValueError :
[1332]1567                pass
[3413]1568            else :
[1332]1569                fields = {
1570                           "uniqueMember" : parent.uniqueMember,
[3413]1571                         }
1572                self.doModify(parent.ident, fields)
1573        self.doDelete(printer.ident)
1574
[2358]1575    def deleteBillingCode(self, code) :
1576        """Deletes a billing code from the Quota Storage (no entries are deleted from the history)"""
1577        self.doDelete(code.ident)
[3413]1578
1579    def sortRecords(self, fields, records, default, ordering) :
[3167]1580        """Sort records based on list of fields prefixed with '+' (ASC) or '-' (DESC)."""
1581        fieldindexes = {}
1582        for i in range(len(fields)) :
1583            fieldindexes[fields[i]] = i
[3413]1584        if not ordering :
[3167]1585            ordering = default
[3413]1586        orderby = []
[3167]1587        for orderkey in ordering :
[3168]1588            # Create ordering hints, ignoring unknown fields
[3167]1589            if orderkey.startswith("-") :
[3168]1590                index = fieldindexes.get(orderkey[1:])
1591                if index is not None :
1592                    orderby.append((-1, index))
[3167]1593            elif orderkey.startswith("+") :
[3168]1594                index = fieldindexes.get(orderkey[1:])
1595                if index is not None :
1596                    orderby.append((+1, index))
[3413]1597            else :
[3168]1598                index = fieldindexes.get(orderkey)
1599                if index is not None :
1600                    orderby.append((+1, index))
[3413]1601
1602        def compare(x, y, orderby=orderby) :
[3167]1603            """Compares two records."""
1604            i = 0
1605            nbkeys = len(orderby)
1606            while i < nbkeys :
1607                (sign, index) = orderby[i]
1608                result = cmp(x[i], y[i])
1609                if not result :
1610                    i += 1
[3413]1611                else :
[3167]1612                    return sign * result
[3413]1613            return 0 # identical keys
1614
[3167]1615        records.sort(compare)
1616        return records
[3413]1617
[3165]1618    def extractPrinters(self, extractonly={}, ordering=[]) :
[1754]1619        """Extracts all printer records."""
[1993]1620        pname = extractonly.get("printername")
1621        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1622        if entries :
[3167]1623            fields = ("dn", "printername", "priceperpage", "priceperjob", "description", "maxjobsize", "passthrough")
1624            result = []
[1754]1625            for entry in entries :
[2459]1626                if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") :
1627                    passthrough = "t"
[3413]1628                else :
[2459]1629                    passthrough = "f"
1630                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description, entry.MaxJobSize, passthrough))
[3413]1631            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
1632
[3165]1633    def extractUsers(self, extractonly={}, ordering=[]) :
[1754]1634        """Extracts all user records."""
[1993]1635        uname = extractonly.get("username")
1636        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
[1754]1637        if entries :
[3167]1638            fields = ("dn", "username", "balance", "lifetimepaid", "limitby", "email", "description", "overcharge")
1639            result = []
[1754]1640            for entry in entries :
[3086]1641                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email, entry.Description, entry.OverCharge))
[3167]1642            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[3413]1643
[3165]1644    def extractBillingcodes(self, extractonly={}, ordering=[]) :
[2358]1645        """Extracts all billing codes records."""
1646        billingcode = extractonly.get("billingcode")
1647        entries = [b for b in [self.getBillingCode(label) for label in self.getAllBillingCodes(billingcode)] if b.Exists]
1648        if entries :
[3167]1649            fields = ("dn", "billingcode", "balance", "pagecounter", "description")
1650            result = []
[2358]1651            for entry in entries :
1652                result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description))
[3167]1653            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[3413]1654
[3165]1655    def extractGroups(self, extractonly={}, ordering=[]) :
[1754]1656        """Extracts all group records."""
[1993]1657        gname = extractonly.get("groupname")
1658        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
[1754]1659        if entries :
[3167]1660            fields = ("dn", "groupname", "limitby", "balance", "lifetimepaid", "description")
1661            result = []
[1754]1662            for entry in entries :
[2721]1663                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid, entry.Description))
[3167]1664            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[3413]1665
[3165]1666    def extractPayments(self, extractonly={}, ordering=[]) :
[1754]1667        """Extracts all payment records."""
[3142]1668        startdate = extractonly.get("start")
1669        enddate = extractonly.get("end")
1670        (startdate, enddate) = self.cleanDates(startdate, enddate)
[1993]1671        uname = extractonly.get("username")
1672        entries = [u for u in [self.getUser(name) for name in self.getAllUsersNames(uname)] if u.Exists]
[1765]1673        if entries :
[3167]1674            fields = ("username", "amount", "date", "description")
1675            result = []
[1765]1676            for entry in entries :
[2452]1677                for (date, amount, description) in entry.Payments :
[3142]1678                    if ((startdate is None) and (enddate is None)) or \
1679                       ((startdate is None) and (date <= enddate)) or \
1680                       ((enddate is None) and (date >= startdate)) or \
1681                       ((date >= startdate) and (date <= enddate)) :
1682                        result.append((entry.Name, amount, date, description))
[3167]1683            return [fields] + self.sortRecords(fields, result, ["+date"], ordering)
[3413]1684
[3165]1685    def extractUpquotas(self, extractonly={}, ordering=[]) :
[1754]1686        """Extracts all userpquota records."""
[1993]1687        pname = extractonly.get("printername")
1688        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1689        if entries :
[3549]1690            fields = ("username", "printername", "dn", "userdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit", "maxjobsize")
[3167]1691            result = []
[2040]1692            uname = extractonly.get("username")
[1764]1693            for entry in entries :
[2041]1694                for (user, userpquota) in self.getPrinterUsersAndQuotas(entry, names=[uname or "*"]) :
[3549]1695                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit, userpquota.MaxJobSize))
[3167]1696            return [fields] + self.sortRecords(fields, result, ["+userdn"], ordering)
[3413]1697
[3165]1698    def extractGpquotas(self, extractonly={}, ordering=[]) :
[1754]1699        """Extracts all grouppquota records."""
[1993]1700        pname = extractonly.get("printername")
1701        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1702        if entries :
[3167]1703            fields = ("groupname", "printername", "dn", "groupdn", "printerdn", "lifepagecounter", "pagecounter", "softlimit", "hardlimit", "datelimit")
1704            result = []
[1993]1705            gname = extractonly.get("groupname")
[1764]1706            for entry in entries :
[2042]1707                for (group, grouppquota) in self.getPrinterGroupsAndQuotas(entry, names=[gname or "*"]) :
1708                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
[3167]1709            return [fields] + self.sortRecords(fields, result, ["+groupdn"], ordering)
[3413]1710
[3165]1711    def extractUmembers(self, extractonly={}, ordering=[]) :
[1754]1712        """Extracts all user groups members."""
[1993]1713        gname = extractonly.get("groupname")
1714        entries = [g for g in [self.getGroup(name) for name in self.getAllGroupsNames(gname)] if g.Exists]
[1754]1715        if entries :
[3167]1716            fields = ("groupname", "username", "groupdn", "userdn")
1717            result = []
[1993]1718            uname = extractonly.get("username")
[1754]1719            for entry in entries :
1720                for member in entry.Members :
[1993]1721                    if (uname is None) or (member.Name == uname) :
1722                        result.append((entry.Name, member.Name, entry.ident, member.ident))
[3167]1723            return [fields] + self.sortRecords(fields, result, ["+groupdn", "+userdn"], ordering)
[3413]1724
[3165]1725    def extractPmembers(self, extractonly={}, ordering=[]) :
[1754]1726        """Extracts all printer groups members."""
[1993]1727        pname = extractonly.get("printername")
1728        entries = [p for p in [self.getPrinter(name) for name in self.getAllPrintersNames(pname)] if p.Exists]
[1754]1729        if entries :
[3167]1730            fields = ("pgroupname", "printername", "pgroupdn", "printerdn")
1731            result = []
[1993]1732            pgname = extractonly.get("pgroupname")
[1754]1733            for entry in entries :
1734                for parent in self.getParentPrinters(entry) :
[1993]1735                    if (pgname is None) or (parent.Name == pgname) :
1736                        result.append((parent.Name, entry.Name, parent.ident, entry.ident))
[3167]1737            return [fields] + self.sortRecords(fields, result, ["+pgroupdn", "+printerdn"], ordering)
[3413]1738
[3165]1739    def extractHistory(self, extractonly={}, ordering=[]) :
[1754]1740        """Extracts all jobhistory records."""
[1993]1741        uname = extractonly.get("username")
1742        if uname :
1743            user = self.getUser(uname)
[3413]1744        else :
[1993]1745            user = None
1746        pname = extractonly.get("printername")
1747        if pname :
1748            printer = self.getPrinter(pname)
[3413]1749        else :
[1993]1750            printer = None
[2266]1751        startdate = extractonly.get("start")
1752        enddate = extractonly.get("end")
1753        (startdate, enddate) = self.cleanDates(startdate, enddate)
[3091]1754        entries = self.retrieveHistory(user, printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), jobid=extractonly.get("jobid"), limit=None, start=startdate, end=enddate)
[1754]1755        if entries :
[3167]1756            fields = ("username", "printername", "dn", "jobid", "pagecounter", "jobsize", "action", "jobdate", "filename", "title", "copies", "options", "jobprice", "hostname", "jobsizebytes", "md5sum", "pages", "billingcode", "precomputedjobsize", "precomputedjobprice")
1757            result = []
[1754]1758            for entry in entries :
[3413]1759                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]1760            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
[3413]1761
[2372]1762    def getBillingCodeFromBackend(self, label) :
1763        """Extracts billing code information given its label : returns first matching billing code."""
1764        code = StorageBillingCode(self, label)
[3294]1765        ulabel = unicodeToDatabase(label)
[2652]1766        result = self.doSearch("(&(objectClass=pykotaBilling)(pykotaBillingCode=%s))" % \
1767                                  ulabel, \
1768                                  ["pykotaBillingCode", "pykotaBalance", "pykotaPageCounter", "description"], \
1769                                  base=self.info["billingcodebase"])
[2372]1770        if result :
1771            fields = result[0][1]       # take only first matching code, ignore the rest
1772            code.ident = result[0][0]
[3294]1773            code.BillingCode = databaseToUnicode(fields.get("pykotaBillingCode", [ulabel])[0])
[2372]1774            code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1775            code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
[3413]1776            code.Description = databaseToUnicode(fields.get("description", [""])[0])
[2950]1777            code.Exists = True
[3413]1778        return code
1779
[2765]1780    def addBillingCode(self, bcode) :
[2375]1781        """Adds a billing code to the quota storage, returns it."""
[2765]1782        oldentry = self.getBillingCode(bcode.BillingCode)
1783        if oldentry.Exists :
1784            return oldentry # we return the existing entry
[2375]1785        uuid = self.genUUID()
1786        dn = "cn=%s,%s" % (uuid, self.info["billingcodebase"])
1787        fields = { "objectClass" : ["pykotaObject", "pykotaBilling"],
1788                   "cn" : uuid,
[3294]1789                   "pykotaBillingCode" : unicodeToDatabase(bcode.BillingCode),
[2765]1790                   "pykotaPageCounter" : str(bcode.PageCounter or 0),
1791                   "pykotaBalance" : str(bcode.Balance or 0.0),
[3413]1792                   "description" : unicodeToDatabase(bcode.Description or ""),
1793                 }
[2375]1794        self.doAdd(dn, fields)
[2765]1795        bcode.isDirty = False
1796        return None # the entry created doesn't need further modification
[3413]1797
[2765]1798    def saveBillingCode(self, bcode) :
[2375]1799        """Sets the new description for a billing code."""
1800        fields = {
[3413]1801                   "description" : unicodeToDatabase(bcode.Description or ""),
[2765]1802                   "pykotaPageCounter" : str(bcode.PageCounter or 0),
1803                   "pykotaBalance" : str(bcode.Balance or 0.0),
[2375]1804                 }
[2765]1805        self.doModify(bcode.ident, fields)
[3413]1806
[2380]1807    def getMatchingBillingCodes(self, billingcodepattern) :
1808        """Returns the list of all billing codes which match a certain pattern."""
1809        codes = []
[2657]1810        result = self.doSearch("objectClass=pykotaBilling", \
[2380]1811                                ["pykotaBillingCode", "description", "pykotaPageCounter", "pykotaBalance"], \
1812                                base=self.info["billingcodebase"])
1813        if result :
[2657]1814            patterns = billingcodepattern.split(",")
[3521]1815            patdict = {}.fromkeys(patterns)
[2380]1816            for (codeid, fields) in result :
[3294]1817                codename = databaseToUnicode(fields.get("pykotaBillingCode", [""])[0])
[2776]1818                if patdict.has_key(codename) or self.tool.matchString(codename, patterns) :
[2657]1819                    code = StorageBillingCode(self, codename)
1820                    code.ident = codeid
1821                    code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
1822                    code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
[3413]1823                    code.Description = databaseToUnicode(fields.get("description", [""])[0])
[2950]1824                    code.Exists = True
[2657]1825                    codes.append(code)
1826                    self.cacheEntry("BILLINGCODES", code.BillingCode, code)
[3413]1827        return codes
1828
[2765]1829    def consumeBillingCode(self, bcode, pagecounter, balance) :
[2384]1830        """Consumes from a billing code."""
1831        fields = {
1832                   "pykotaBalance" : { "operator" : "-", "value" : balance, "convert" : float },
1833                   "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int },
1834                 }
[3413]1835        return self.doModify(bcode.ident, fields)
[2358]1836
[3413]1837    def refundJob(self, jobident) :
[3056]1838        """Marks a job as refunded in the history."""
1839        fields = {
1840                     "pykotaAction" : "REFUND",
[3413]1841                 }
1842        self.doModify(jobident, fields)
1843
[2950]1844    def storageUserFromRecord(self, username, record) :
1845        """Returns a StorageUser instance from a database record."""
1846        user = StorageUser(self, username)
1847        user.Exists = True
1848        return user
[3413]1849
[2950]1850    def storageGroupFromRecord(self, groupname, record) :
1851        """Returns a StorageGroup instance from a database record."""
1852        group = StorageGroup(self, groupname)
1853        group.Exists = True
1854        return group
[3413]1855
[2950]1856    def storagePrinterFromRecord(self, printername, record) :
1857        """Returns a StoragePrinter instance from a database record."""
1858        printer = StoragePrinter(self, printername)
1859        printer.Exists = True
1860        return printer
[3413]1861
1862    def setJobAttributesFromRecord(self, job, record) :
[2950]1863        """Sets the attributes of a job from a database record."""
1864        job.Exists = True
[3413]1865
[2950]1866    def storageJobFromRecord(self, record) :
1867        """Returns a StorageJob instance from a database record."""
1868        job = StorageJob(self)
1869        self.setJobAttributesFromRecord(job, record)
1870        return job
[3413]1871
[2950]1872    def storageLastJobFromRecord(self, printer, record) :
1873        """Returns a StorageLastJob instance from a database record."""
1874        lastjob = StorageLastJob(self, printer)
1875        self.setJobAttributesFromRecord(lastjob, record)
1876        return lastjob
[3413]1877
[2950]1878    def storageUserPQuotaFromRecord(self, user, printer, record) :
1879        """Returns a StorageUserPQuota instance from a database record."""
1880        userpquota = StorageUserPQuota(self, user, printer)
1881        userpquota.Exists = True
1882        return userpquota
[3413]1883
[2950]1884    def storageGroupPQuotaFromRecord(self, group, printer, record) :
1885        """Returns a StorageGroupPQuota instance from a database record."""
1886        grouppquota = StorageGroupPQuota(self, group, printer)
1887        grouppquota.Exists = True
1888        return grouppquota
[3413]1889
[2950]1890    def storageBillingCodeFromRecord(self, billingcode, record) :
1891        """Returns a StorageBillingCode instance from a database record."""
1892        code = StorageBillingCode(self, billingcode)
1893        code.Exists = True
1894        return code
Note: See TracBrowser for help on using the browser.