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

Revision 2146, 78.0 kB (checked in by jerome, 19 years ago)

It seems that $Log$ is not implemented or doesn't work for some reason

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