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

Revision 2000, 75.3 kB (checked in by jalet, 19 years ago)

Implemented in the LDAP backend code the same fix than in PostgreSQL backend
code for people who don't use PyKota tools to manage accounts.

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