root / pykota / trunk / pykota / storage.py @ 2054

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

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

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3#
4# PyKota : Print Quotas for CUPS and LPRng
5#
6# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23# $Log$
24# Revision 1.68  2005/02/13 22:02:29  jalet
25# Big database structure changes. Upgrade script is now included as well as
26# the new LDAP schema.
27# Introduction of the -o | --overcharge command line option to edpykota.
28# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
29# Introduction of the new 'maxdenybanners' directive.
30#
31# Revision 1.67  2005/01/18 19:47:50  jalet
32# Big bug fix wrt the datelimit attribute
33#
34# Revision 1.66  2004/12/02 21:24:50  jalet
35# Integrated the patch by Wilson Roberto Afonso and Matt Hyclak to allow
36# edpykota to accept the -U | --used value command line option.
37#
38# Revision 1.65  2004/10/25 14:12:25  jalet
39# For URGENT legal reasons (Italy), a new "privacy" directive was added to pykota.conf
40# to hide print jobs' title, filename, and options.
41#
42# Revision 1.64  2004/10/08 20:19:18  jalet
43# Added ugly workaround for strange locale problem
44#
45# Revision 1.63  2004/10/07 21:14:28  jalet
46# Hopefully final fix for data encoding to and from the database
47#
48# Revision 1.62  2004/09/28 17:45:31  jalet
49# Added the --hardreset command line option to edpykota
50#
51# Revision 1.61  2004/09/24 21:19:48  jalet
52# Did a pass of PyChecker
53#
54# Revision 1.60  2004/09/24 20:21:50  jalet
55# Fixed pykotaAccountBalance object location during creation
56#
57# Revision 1.59  2004/09/10 21:32:52  jalet
58# Small fixes for incomplete entry intialization
59#
60# Revision 1.58  2004/09/02 10:09:30  jalet
61# Fixed bug in LDAP user deletion code which didn't correctly delete the user's
62# pykotaLastJob entries.
63#
64# Revision 1.57  2004/07/22 22:41:48  jalet
65# Hardware accounting for LPRng should be OK now. UNTESTED.
66#
67# Revision 1.56  2004/07/01 17:45:49  jalet
68# Added code to handle the description field for printers
69#
70# Revision 1.55  2004/06/05 22:18:04  jalet
71# Now catches some exceptions earlier.
72# storage.py and ldapstorage.py : removed old comments
73#
74# Revision 1.54  2004/06/05 22:03:49  jalet
75# Payments history is now stored in database
76#
77# Revision 1.53  2004/06/03 23:14:10  jalet
78# Now stores the job's size in bytes in the database.
79# Preliminary work on payments storage : database schemas are OK now,
80# but no code to store payments yet.
81# Removed schema picture, not relevant anymore.
82#
83# Revision 1.52  2004/05/26 14:49:57  jalet
84# First try at saving the job-originating-hostname in the database
85#
86# Revision 1.51  2004/03/24 15:15:24  jalet
87# Began integration of Henrik Janhagen's work on quota-then-balance
88# and balance-then-quota
89#
90# Revision 1.50  2004/03/09 08:05:27  jalet
91# Small fix : only keeps existing quota entries when searching parents
92#
93# Revision 1.49  2004/03/01 15:06:51  jalet
94# Pre and Post hooks should now work in the pykota filter too.
95# The pykota filter doesn't check the last user's quota anymore
96# when delayed hardware accounting is used : this will be checked
97# anyway the next time the last user will print
98#
99# Revision 1.48  2004/02/27 13:50:12  jalet
100# Hopefully the final fix for groups (users and printers)
101#
102# Revision 1.47  2004/02/27 09:30:33  jalet
103# datelimit wasn't reset when modifying soft and hard limits with the LDAP backend
104#
105# Revision 1.46  2004/02/26 14:18:07  jalet
106# Should fix the remaining bugs wrt printers groups and users groups.
107#
108# Revision 1.45  2004/02/26 10:40:40  jalet
109# Fixed nested printer groups accounting.
110#
111# Revision 1.44  2004/02/25 19:09:24  jalet
112# Fix for LDAP problem when job price was 0.
113#
114# Revision 1.43  2004/02/25 12:36:34  jalet
115# Avoids a database query even if caching was disabled.
116#
117# Revision 1.42  2004/02/23 22:53:21  jalet
118# Don't retrieve data when it's not needed, to avoid database queries
119#
120# Revision 1.41  2004/02/04 17:12:33  jalet
121# Removing a printer from a printers group should work now.
122#
123# Revision 1.40  2004/02/04 13:24:41  jalet
124# pkprinters can now remove printers from printers groups.
125#
126# Revision 1.39  2004/02/04 11:16:59  jalet
127# pkprinters command line tool added.
128#
129# Revision 1.38  2004/01/12 22:43:40  jalet
130# New formula to compute a job's price
131#
132# Revision 1.37  2004/01/12 14:35:01  jalet
133# Printing history added to CGI script.
134#
135# Revision 1.36  2004/01/10 09:44:02  jalet
136# Fixed potential accuracy problem if a user printed on several printers at
137# the very same time.
138#
139# Revision 1.35  2004/01/08 16:33:27  jalet
140# Additionnal check to not create a circular printers group.
141#
142# Revision 1.34  2004/01/08 16:24:49  jalet
143# edpykota now supports adding printers to printer groups.
144#
145# Revision 1.33  2004/01/08 14:10:32  jalet
146# Copyright year changed.
147#
148# Revision 1.32  2004/01/06 16:02:57  jalet
149# This time printer groups caching works.
150#
151# Revision 1.31  2004/01/06 15:51:24  jalet
152# Fixed caching of printer groups
153#
154# Revision 1.30  2004/01/06 14:24:59  jalet
155# Printer groups should be cached now, if caching is enabled.
156#
157# Revision 1.29  2003/12/27 16:49:25  uid67467
158# Should be ok now.
159#
160# Revision 1.28  2003/11/25 23:46:40  jalet
161# Don't try to verify if module name is valid, Python does this better than us.
162#
163# Revision 1.27  2003/11/23 19:01:36  jalet
164# Job price added to history
165#
166# Revision 1.26  2003/11/21 14:28:45  jalet
167# More complete job history.
168#
169# Revision 1.25  2003/10/08 21:12:27  jalet
170# Do not cache anymore entries which don't exist.
171#
172# Revision 1.24  2003/10/07 22:06:05  jalet
173# Preliminary code to disable job history
174#
175# Revision 1.23  2003/10/07 09:07:28  jalet
176# Character encoding added to please latest version of Python
177#
178# Revision 1.22  2003/10/06 13:12:27  jalet
179# More work on caching
180#
181# Revision 1.21  2003/10/03 09:02:20  jalet
182# Logs cache store actions too
183#
184# Revision 1.20  2003/10/02 20:23:18  jalet
185# Storage caching mechanism added.
186#
187# Revision 1.19  2003/07/16 21:53:07  jalet
188# Really big modifications wrt new configuration file's location and content.
189#
190# Revision 1.18  2003/07/07 08:33:18  jalet
191# Bug fix due to a typo in LDAP code
192#
193# Revision 1.17  2003/07/05 07:46:50  jalet
194# The previous bug fix was incomplete.
195#
196# Revision 1.16  2003/06/25 19:52:31  jalet
197# Should be ready for testing :-)
198#
199# Revision 1.15  2003/06/25 14:10:58  jalet
200# Exception raising for now.
201#
202# Revision 1.14  2003/06/25 14:10:01  jalet
203# Hey, it may work (edpykota --reset excepted) !
204#
205# Revision 1.13  2003/06/10 16:37:54  jalet
206# Deletion of the second user which is not needed anymore.
207# Added a debug configuration field in /etc/pykota.conf
208# All queries can now be sent to the logger in debug mode, this will
209# greatly help improve performance when time for this will come.
210#
211# Revision 1.12  2003/04/23 22:13:57  jalet
212# Preliminary support for LPRng added BUT STILL UNTESTED.
213#
214# Revision 1.11  2003/04/10 21:47:20  jalet
215# Job history added. Upgrade script neutralized for now !
216#
217# Revision 1.10  2003/03/29 13:45:27  jalet
218# GPL paragraphs were incorrectly (from memory) copied into the sources.
219# Two README files were added.
220# Upgrade script for PostgreSQL pre 1.01 schema was added.
221#
222# Revision 1.9  2003/02/17 22:55:01  jalet
223# More options can now be set per printer or globally :
224#
225#       admin
226#       adminmail
227#       gracedelay
228#       requester
229#
230# the printer option has priority when both are defined.
231#
232# Revision 1.8  2003/02/17 22:05:50  jalet
233# Storage backend now supports admin and user passwords (untested)
234#
235# Revision 1.7  2003/02/10 12:07:31  jalet
236# Now repykota should output the recorded total page number for each printer too.
237#
238# Revision 1.6  2003/02/09 13:05:43  jalet
239# Internationalization continues...
240#
241# Revision 1.5  2003/02/08 22:39:46  jalet
242# --reset command line option added
243#
244# Revision 1.4  2003/02/08 09:59:59  jalet
245# Added preliminary base class for all storages
246#
247# Revision 1.3  2003/02/05 22:10:29  jalet
248# Typos
249#
250# Revision 1.2  2003/02/05 22:02:22  jalet
251# __import__ statement didn't work as expected
252#
253# Revision 1.1  2003/02/05 21:28:17  jalet
254# Initial import into CVS
255#
256#
257#
258
259class PyKotaStorageError(Exception):
260    """An exception for Quota Storage related stuff."""
261    def __init__(self, message = ""):
262        self.message = message
263        Exception.__init__(self, message)
264    def __repr__(self):
265        return self.message
266    __str__ = __repr__
267       
268class StorageObject :
269    """Object present in the Quota Storage."""
270    def __init__(self, parent) :
271        "Initialize minimal data."""
272        self.parent = parent
273        self.ident = None
274        self.Exists = 0
275       
276class StorageUser(StorageObject) :       
277    """User class."""
278    def __init__(self, parent, name) :
279        StorageObject.__init__(self, parent)
280        self.Name = name
281        self.LimitBy = None
282        self.AccountBalance = None
283        self.LifeTimePaid = None
284        self.Email = None
285        self.OverCharge = 1.0
286        self.Payments = [] # TODO : maybe handle this smartly for SQL, for now just don't retrieve them
287       
288    def consumeAccountBalance(self, amount) :     
289        """Consumes an amount of money from the user's account balance."""
290        self.parent.decreaseUserAccountBalance(self, amount)
291        self.AccountBalance = float(self.AccountBalance or 0.0) - amount
292       
293    def setAccountBalance(self, balance, lifetimepaid) :   
294        """Sets the user's account balance in case he pays more money."""
295        diff = float(lifetimepaid or 0.0) - float(self.LifeTimePaid or 0.0)
296        self.parent.beginTransaction()
297        try :
298            self.parent.writeUserAccountBalance(self, balance, lifetimepaid)
299            self.parent.writeNewPayment(self, diff)
300        except PyKotaStorageError, msg :   
301            self.parent.rollbackTransaction()
302            raise PyKotaStorageError, msg
303        else :   
304            self.parent.commitTransaction()
305            self.AccountBalance = balance
306            self.LifeTimePaid = lifetimepaid
307       
308    def setLimitBy(self, limitby) :   
309        """Sets the user's limiting factor."""
310        try :
311            limitby = limitby.lower()
312        except AttributeError :   
313            limitby = "quota"
314        if limitby in ["quota", "balance", "quota-then-balance", "balance-then-quota"] :
315            self.parent.writeUserLimitBy(self, limitby)
316            self.LimitBy = limitby
317       
318    def setOverChargeFactor(self, factor) :   
319        """Sets the user's overcharging coefficient."""
320        self.parent.writeUserOverCharge(self, factor)
321        self.OverCharge = factor
322       
323    def delete(self) :   
324        """Deletes an user from the Quota Storage."""
325        self.parent.beginTransaction()
326        try :
327            self.parent.deleteUser(self)
328        except PyKotaStorageError, msg :   
329            self.parent.rollbackTransaction()
330            raise PyKotaStorageError, msg
331        else :   
332            self.parent.commitTransaction()
333       
334class StorageGroup(StorageObject) :       
335    """User class."""
336    def __init__(self, parent, name) :
337        StorageObject.__init__(self, parent)
338        self.Name = name
339        self.LimitBy = None
340        self.AccountBalance = None
341        self.LifeTimePaid = None
342       
343    def setLimitBy(self, limitby) :   
344        """Sets the user's limiting factor."""
345        try :
346            limitby = limitby.lower()
347        except AttributeError :   
348            limitby = "quota"
349        if limitby in ["quota", "balance"] :
350            self.parent.writeGroupLimitBy(self, limitby)
351            self.LimitBy = limitby
352       
353    def delete(self) :   
354        """Deletes a group from the Quota Storage."""
355        self.parent.beginTransaction()
356        try :
357            self.parent.deleteGroup(self)
358        except PyKotaStorageError, msg :   
359            self.parent.rollbackTransaction()
360            raise PyKotaStorageError, msg
361        else :   
362            self.parent.commitTransaction()
363       
364class StoragePrinter(StorageObject) :
365    """Printer class."""
366    def __init__(self, parent, name) :
367        StorageObject.__init__(self, parent)
368        self.Name = name
369        self.PricePerPage = None
370        self.PricePerJob = None
371        self.Description = None
372        self.Coefficients = None
373       
374    def __getattr__(self, name) :   
375        """Delays data retrieval until it's really needed."""
376        if name == "LastJob" : 
377            self.LastJob = self.parent.getPrinterLastJob(self)
378            return self.LastJob
379        else :
380            raise AttributeError, name
381           
382    def addJobToHistory(self, jobid, user, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None) :
383        """Adds a job to the printer's history."""
384        self.parent.writeJobNew(self, user, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, clienthost, jobsizebytes)
385        # TODO : update LastJob object ? Probably not needed.
386       
387    def addPrinterToGroup(self, printer) :   
388        """Adds a printer to a printer group."""
389        if (printer not in self.parent.getParentPrinters(self)) and (printer.ident != self.ident) :
390            self.parent.writePrinterToGroup(self, printer)
391            # TODO : reset cached value for printer parents, or add new parent to cached value
392           
393    def delPrinterFromGroup(self, printer) :   
394        """Deletes a printer from a printer group."""
395        self.parent.removePrinterFromGroup(self, printer)
396        # TODO : reset cached value for printer parents, or add new parent to cached value
397       
398    def setPrices(self, priceperpage = None, priceperjob = None) :   
399        """Sets the printer's prices."""
400        if priceperpage is None :
401            priceperpage = self.PricePerPage or 0.0
402        else :   
403            self.PricePerPage = float(priceperpage)
404        if priceperjob is None :   
405            priceperjob = self.PricePerJob or 0.0
406        else :   
407            self.PricePerJob = float(priceperjob)
408        self.parent.writePrinterPrices(self)
409       
410    def setDescription(self, description = None) :   
411        """Sets the printer's prices."""
412        if description is None :
413            description = self.Description
414        else :   
415            self.Description = str(description)
416        self.parent.writePrinterDescription(self)
417       
418    def delete(self) :   
419        """Deletes a printer from the Quota Storage."""
420        self.parent.beginTransaction()
421        try :
422            self.parent.deletePrinter(self)
423        except PyKotaStorageError, msg :   
424            self.parent.rollbackTransaction()
425            raise PyKotaStorageError, msg
426        else :   
427            self.parent.commitTransaction()
428       
429class StorageUserPQuota(StorageObject) :
430    """User Print Quota class."""
431    def __init__(self, parent, user, printer) :
432        StorageObject.__init__(self, parent)
433        self.User = user
434        self.Printer = printer
435        self.PageCounter = None
436        self.LifePageCounter = None
437        self.SoftLimit = None
438        self.HardLimit = None
439        self.DateLimit = None
440        self.WarnCount = None
441       
442    def __getattr__(self, name) :   
443        """Delays data retrieval until it's really needed."""
444        if name == "ParentPrintersUserPQuota" : 
445            self.ParentPrintersUserPQuota = (self.User.Exists and self.Printer.Exists and self.parent.getParentPrintersUserPQuota(self)) or []
446            return self.ParentPrintersUserPQuota
447        else :
448            raise AttributeError, name
449       
450    def setDateLimit(self, datelimit) :   
451        """Sets the date limit for this quota."""
452        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)
453        self.parent.writeUserPQuotaDateLimit(self, date)
454        self.DateLimit = date
455       
456    def setLimits(self, softlimit, hardlimit) :   
457        """Sets the soft and hard limit for this quota."""
458        self.parent.writeUserPQuotaLimits(self, softlimit, hardlimit)
459        self.SoftLimit = softlimit
460        self.HardLimit = hardlimit
461        self.DateLimit = None
462        self.WarnCount = 0
463       
464    def setUsage(self, used) :
465        """Sets the PageCounter and LifePageCounter to used, or if used is + or - prefixed, changes the values of {Life,}PageCounter by that amount."""
466        vused = int(used)
467        if used.startswith("+") or used.startswith("-") :
468            self.parent.beginTransaction()
469            try :
470                self.parent.increaseUserPQuotaPagesCounters(self, vused)
471                self.parent.writeUserPQuotaDateLimit(self, None)
472                self.parent.writeUserPQuotaWarnCount(self, 0)
473            except PyKotaStorageError, msg :   
474                self.parent.rollbackTransaction()
475                raise PyKotaStorageError, msg
476            else :
477                self.parent.commitTransaction()
478            self.PageCounter += vused
479            self.LifePageCounter += vused
480        else :
481            self.parent.writeUserPQuotaPagesCounters(self, vused, vused)
482            self.PageCounter = self.LifePageCounter = vused
483        self.DateLimit = None
484        self.WarnCount = 0
485
486    def warn(self) :
487        """Increases the warn counter for this user quota."""
488        self.parent.increaseUserPQuotaWarnCount(self)
489        self.WarnCount = (self.WarnCount or 0) + 1
490       
491    def reset(self) :   
492        """Resets page counter to 0."""
493        self.parent.writeUserPQuotaPagesCounters(self, 0, int(self.LifePageCounter or 0))
494        self.PageCounter = 0
495        self.DateLimit = None
496       
497    def hardreset(self) :   
498        """Resets actual and life time page counters to 0."""
499        self.parent.writeUserPQuotaPagesCounters(self, 0, 0)
500        self.PageCounter = self.LifePageCounter = 0
501        self.DateLimit = None
502       
503    def computeJobPrice(self, jobsize) :   
504        """Computes the job price as the sum of all parent printers' prices + current printer's ones."""
505        totalprice = 0.0   
506        if jobsize :
507            if self.User.OverCharge != 0.0 :    # optimization, but TODO : beware of rounding errors
508                for upq in [ self ] + self.ParentPrintersUserPQuota :
509                    price = (float(upq.Printer.PricePerPage or 0.0) * jobsize) + float(upq.Printer.PricePerJob or 0.0)
510                    totalprice += price
511        if self.User.OverCharge != 1.0 : # TODO : beware of rounding errors
512            overcharged = totalprice * self.User.OverCharge       
513            self.parent.tool.printInfo("Overcharging %s by a factor of %s ===> User %s will be charged for %s units." % (totalprice, self.User.OverCharge, self.User.Name, overcharged))
514            return overcharged
515        else :   
516            return totalprice
517           
518    def increasePagesUsage(self, jobsize) :
519        """Increase the value of used pages and money."""
520        jobprice = self.computeJobPrice(jobsize)
521        if jobsize :
522            self.parent.beginTransaction()
523            try :
524                if jobprice :
525                    self.User.consumeAccountBalance(jobprice)
526                for upq in [ self ] + self.ParentPrintersUserPQuota :
527                    self.parent.increaseUserPQuotaPagesCounters(upq, jobsize)
528                    upq.PageCounter = int(upq.PageCounter or 0) + jobsize
529                    upq.LifePageCounter = int(upq.LifePageCounter or 0) + jobsize
530            except PyKotaStorageError, msg :   
531                self.parent.rollbackTransaction()
532                raise PyKotaStorageError, msg
533            else :   
534                self.parent.commitTransaction()
535        return jobprice
536       
537class StorageGroupPQuota(StorageObject) :
538    """Group Print Quota class."""
539    def __init__(self, parent, group, printer) :
540        StorageObject.__init__(self, parent)
541        self.Group = group
542        self.Printer = printer
543        self.PageCounter = None
544        self.LifePageCounter = None
545        self.SoftLimit = None
546        self.HardLimit = None
547        self.DateLimit = None
548       
549    def __getattr__(self, name) :   
550        """Delays data retrieval until it's really needed."""
551        if name == "ParentPrintersGroupPQuota" : 
552            self.ParentPrintersGroupPQuota = (self.Group.Exists and self.Printer.Exists and self.parent.getParentPrintersGroupPQuota(self)) or []
553            return self.ParentPrintersGroupPQuota
554        else :
555            raise AttributeError, name
556       
557    def reset(self) :   
558        """Resets page counter to 0."""
559        self.parent.beginTransaction()
560        try :
561            for user in self.parent.getGroupMembers(self.Group) :
562                uq = self.parent.getUserPQuota(user, self.Printer)
563                uq.reset()
564            self.parent.writeGroupPQuotaDateLimit(self, None)
565        except PyKotaStorageError, msg :   
566            self.parent.rollbackTransaction()
567            raise PyKotaStorageError, msg
568        else :   
569            self.parent.commitTransaction()
570        self.PageCounter = 0
571        self.DateLimit = None
572       
573    def hardreset(self) :   
574        """Resets actual and life time page counters to 0."""
575        self.parent.beginTransaction()
576        try :
577            for user in self.parent.getGroupMembers(self.Group) :
578                uq = self.parent.getUserPQuota(user, self.Printer)
579                uq.hardreset()
580            self.parent.writeGroupPQuotaDateLimit(self, None)
581        except PyKotaStorageError, msg :   
582            self.parent.rollbackTransaction()
583            raise PyKotaStorageError, msg
584        else :   
585            self.parent.commitTransaction()
586        self.PageCounter = self.LifePageCounter = 0
587        self.DateLimit = None
588       
589    def setDateLimit(self, datelimit) :   
590        """Sets the date limit for this quota."""
591        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)
592        self.parent.writeGroupPQuotaDateLimit(self, date)
593        self.DateLimit = date
594       
595    def setLimits(self, softlimit, hardlimit) :   
596        """Sets the soft and hard limit for this quota."""
597        self.parent.writeGroupPQuotaLimits(self, softlimit, hardlimit)
598        self.SoftLimit = softlimit
599        self.HardLimit = hardlimit
600        self.DateLimit = None
601       
602class StorageJob(StorageObject) :
603    """Printer's Job class."""
604    def __init__(self, parent) :
605        StorageObject.__init__(self, parent)
606        self.UserName = None
607        self.PrinterName = None
608        self.JobId = None
609        self.PrinterPageCounter = None
610        self.JobSizeBytes = None
611        self.JobSize = None
612        self.JobAction = None
613        self.JobDate = None
614        self.JobPrice = None
615        self.JobFileName = None
616        self.JobTitle = None
617        self.JobCopies = None
618        self.JobOptions = None
619        self.JobHostName = None
620        self.JobMD5Sum = None
621        self.JobPages = None
622        self.JobBillingCode = None
623       
624    def __getattr__(self, name) :   
625        """Delays data retrieval until it's really needed."""
626        if name == "User" : 
627            self.User = self.parent.getUser(self.UserName)
628            return self.User
629        elif name == "Printer" :   
630            self.Printer = self.parent.getPrinter(self.PrinterName)
631            return self.Printer
632        else :
633            raise AttributeError, name
634       
635class StorageLastJob(StorageJob) :
636    """Printer's Last Job class."""
637    def __init__(self, parent, printer) :
638        StorageJob.__init__(self, parent)
639        self.PrinterName = printer.Name # not needed
640        self.Printer = printer
641       
642class BaseStorage :
643    def __init__(self, pykotatool) :
644        """Opens the storage connection."""
645        self.closed = 1
646        self.tool = pykotatool
647        self.usecache = pykotatool.config.getCaching()
648        self.disablehistory = pykotatool.config.getDisableHistory()
649        self.privacy = pykotatool.config.getPrivacy()
650        if self.privacy :
651            pykotatool.logdebug("Jobs' title, filename and options will be hidden because of privacy concerns.")
652        if self.usecache :
653            self.tool.logdebug("Caching enabled.")
654            self.caches = { "USERS" : {}, "GROUPS" : {}, "PRINTERS" : {}, "USERPQUOTAS" : {}, "GROUPPQUOTAS" : {}, "JOBS" : {}, "LASTJOBS" : {} }
655       
656    def close(self) :   
657        """Must be overriden in children classes."""
658        raise RuntimeError, "BaseStorage.close() must be overriden !"
659       
660    def __del__(self) :       
661        """Ensures that the database connection is closed."""
662        self.close()
663       
664    def getFromCache(self, cachetype, key) :
665        """Tries to extract something from the cache."""
666        if self.usecache :
667            entry = self.caches[cachetype].get(key)
668            if entry is not None :
669                self.tool.logdebug("Cache hit (%s->%s)" % (cachetype, key))
670            else :   
671                self.tool.logdebug("Cache miss (%s->%s)" % (cachetype, key))
672            return entry   
673           
674    def cacheEntry(self, cachetype, key, value) :       
675        """Puts an entry in the cache."""
676        if self.usecache and getattr(value, "Exists", 0) :
677            self.caches[cachetype][key] = value
678            self.tool.logdebug("Cache store (%s->%s)" % (cachetype, key))
679           
680    def getUser(self, username) :       
681        """Returns the user from cache."""
682        user = self.getFromCache("USERS", username)
683        if user is None :
684            user = self.getUserFromBackend(username)
685            self.cacheEntry("USERS", username, user)
686        return user   
687       
688    def getGroup(self, groupname) :       
689        """Returns the group from cache."""
690        group = self.getFromCache("GROUPS", groupname)
691        if group is None :
692            group = self.getGroupFromBackend(groupname)
693            self.cacheEntry("GROUPS", groupname, group)
694        return group   
695       
696    def getPrinter(self, printername) :       
697        """Returns the printer from cache."""
698        printer = self.getFromCache("PRINTERS", printername)
699        if printer is None :
700            printer = self.getPrinterFromBackend(printername)
701            self.cacheEntry("PRINTERS", printername, printer)
702        return printer   
703       
704    def getUserPQuota(self, user, printer) :       
705        """Returns the user quota information from cache."""
706        useratprinter = "%s@%s" % (user.Name, printer.Name)
707        upquota = self.getFromCache("USERPQUOTAS", useratprinter)
708        if upquota is None :
709            upquota = self.getUserPQuotaFromBackend(user, printer)
710            self.cacheEntry("USERPQUOTAS", useratprinter, upquota)
711        return upquota   
712       
713    def getGroupPQuota(self, group, printer) :       
714        """Returns the group quota information from cache."""
715        groupatprinter = "%s@%s" % (group.Name, printer.Name)
716        gpquota = self.getFromCache("GROUPPQUOTAS", groupatprinter)
717        if gpquota is None :
718            gpquota = self.getGroupPQuotaFromBackend(group, printer)
719            self.cacheEntry("GROUPPQUOTAS", groupatprinter, gpquota)
720        return gpquota   
721       
722    def getPrinterLastJob(self, printer) :       
723        """Extracts last job information for a given printer from cache."""
724        lastjob = self.getFromCache("LASTJOBS", printer.Name)
725        if lastjob is None :
726            lastjob = self.getPrinterLastJobFromBackend(printer)
727            self.cacheEntry("LASTJOBS", printer.Name, lastjob)
728        return lastjob   
729       
730    def getParentPrinters(self, printer) :   
731        """Extracts parent printers information for a given printer from cache."""
732        if self.usecache :
733            if not hasattr(printer, "Parents") :
734                self.tool.logdebug("Cache miss (%s->Parents)" % printer.Name)
735                printer.Parents = self.getParentPrintersFromBackend(printer)
736                self.tool.logdebug("Cache store (%s->Parents)" % printer.Name)
737            else :
738                self.tool.logdebug("Cache hit (%s->Parents)" % printer.Name)
739        else :       
740            printer.Parents = self.getParentPrintersFromBackend(printer)
741        for parent in printer.Parents[:] :   
742            printer.Parents.extend(self.getParentPrinters(parent))
743        uniquedic = {}   
744        for parent in printer.Parents :
745            uniquedic[parent.Name] = parent
746        printer.Parents = uniquedic.values()   
747        return printer.Parents
748       
749    def getGroupMembers(self, group) :       
750        """Returns the group's members list from in-group cache."""
751        if self.usecache :
752            if not hasattr(group, "Members") :
753                self.tool.logdebug("Cache miss (%s->Members)" % group.Name)
754                group.Members = self.getGroupMembersFromBackend(group)
755                self.tool.logdebug("Cache store (%s->Members)" % group.Name)
756            else :
757                self.tool.logdebug("Cache hit (%s->Members)" % group.Name)
758        else :       
759            group.Members = self.getGroupMembersFromBackend(group)
760        return group.Members   
761       
762    def getUserGroups(self, user) :       
763        """Returns the user's groups list from in-user cache."""
764        if self.usecache :
765            if not hasattr(user, "Groups") :
766                self.tool.logdebug("Cache miss (%s->Groups)" % user.Name)
767                user.Groups = self.getUserGroupsFromBackend(user)
768                self.tool.logdebug("Cache store (%s->Groups)" % user.Name)
769            else :
770                self.tool.logdebug("Cache hit (%s->Groups)" % user.Name)
771        else :       
772            user.Groups = self.getUserGroupsFromBackend(user)
773        return user.Groups   
774       
775    def getParentPrintersUserPQuota(self, userpquota) :     
776        """Returns all user print quota on the printer and all its parents recursively."""
777        upquotas = [ ]
778        for printer in self.getParentPrinters(userpquota.Printer) :
779            upq = self.getUserPQuota(userpquota.User, printer)
780            if upq.Exists :
781                upquotas.append(upq)
782        return upquotas       
783       
784    def getParentPrintersGroupPQuota(self, grouppquota) :     
785        """Returns all group print quota on the printer and all its parents recursively."""
786        gpquotas = [ ]
787        for printer in self.getParentPrinters(grouppquota.Printer) :
788            gpq = self.getGroupPQuota(grouppquota.Group, printer)
789            if gpq.Exists :
790                gpquotas.append(gpq)
791        return gpquotas       
792       
793    def databaseToUserCharset(self, text) :
794        """Converts from database format (UTF-8) to user's charset."""
795        if text is not None :
796            try :
797                return unicode(text, "UTF-8").encode(self.tool.getCharset()) 
798            except UnicodeError :   
799                try :
800                    # Incorrect locale settings ?
801                    return unicode(text, "UTF-8").encode("ISO-8859-15") 
802                except UnicodeError :   
803                    pass
804        return text
805       
806    def userCharsetToDatabase(self, text) :
807        """Converts from user's charset to database format (UTF-8)."""
808        if text is not None :
809            try :
810                return unicode(text, self.tool.getCharset()).encode("UTF-8") 
811            except UnicodeError :   
812                try :
813                    # Incorrect locale settings ?
814                    return unicode(text, "ISO-8859-15").encode("UTF-8") 
815                except UnicodeError :   
816                    pass
817        return text
818       
819def openConnection(pykotatool) :
820    """Returns a connection handle to the appropriate Quota Storage Database."""
821    backendinfo = pykotatool.config.getStorageBackend()
822    backend = backendinfo["storagebackend"]
823    try :
824        exec "from pykota.storages import %s as storagebackend" % backend.lower()
825    except ImportError :
826        raise PyKotaStorageError, _("Unsupported quota storage backend %s") % backend
827    else :   
828        host = backendinfo["storageserver"]
829        database = backendinfo["storagename"]
830        admin = backendinfo["storageadmin"] or backendinfo["storageuser"]
831        adminpw = backendinfo["storageadminpw"] or backendinfo["storageuserpw"]
832        return storagebackend.Storage(pykotatool, host, database, admin, adminpw)
Note: See TracBrowser for help on using the browser.