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

Revision 2054, 77.2 kB (checked in by jalet, 19 years ago)

Big database structure changes. Upgrade script is now included as well as
the new LDAP schema.
Introduction of the -o | --overcharge command line option to edpykota.
The output of repykota is more complete, but doesn't fit in 80 columns anymore.
Introduction of the new 'maxdenybanners' directive.

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