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

Revision 3521, 97.7 kB (checked in by jerome, 14 years ago)

Removed some code specific to Python v2.2 and earlier.
Improved the work done by bse@… to fix #52.
IMPORTANT : the same optimisation is currently done for users only, not for
users groups, printers or billing codes. And more importantly the code
was not ported to the LDAP backend. I need more time to do all this.

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