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

Revision 1993, 74.3 kB (checked in by jalet, 19 years ago)

dumpykota's filtering capabilities are now supported within the LDAP
backend as well as within the PostgreSQL backend. Untested though since
my only PyKota+LDAP setup is on my laptop at work :-)

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