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

Revision 1332, 21.3 kB (checked in by jalet, 20 years ago)

pkprinters can now remove printers from printers groups.

  • 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.40  2004/02/04 13:24:41  jalet
25# pkprinters can now remove printers from printers groups.
26#
27# Revision 1.39  2004/02/04 11:16:59  jalet
28# pkprinters command line tool added.
29#
30# Revision 1.38  2004/01/12 22:43:40  jalet
31# New formula to compute a job's price
32#
33# Revision 1.37  2004/01/12 14:35:01  jalet
34# Printing history added to CGI script.
35#
36# Revision 1.36  2004/01/10 09:44:02  jalet
37# Fixed potential accuracy problem if a user printed on several printers at
38# the very same time.
39#
40# Revision 1.35  2004/01/08 16:33:27  jalet
41# Additionnal check to not create a circular printers group.
42#
43# Revision 1.34  2004/01/08 16:24:49  jalet
44# edpykota now supports adding printers to printer groups.
45#
46# Revision 1.33  2004/01/08 14:10:32  jalet
47# Copyright year changed.
48#
49# Revision 1.32  2004/01/06 16:02:57  jalet
50# This time printer groups caching works.
51#
52# Revision 1.31  2004/01/06 15:51:24  jalet
53# Fixed caching of printer groups
54#
55# Revision 1.30  2004/01/06 14:24:59  jalet
56# Printer groups should be cached now, if caching is enabled.
57#
58# Revision 1.29  2003/12/27 16:49:25  uid67467
59# Should be ok now.
60#
61# Revision 1.28  2003/11/25 23:46:40  jalet
62# Don't try to verify if module name is valid, Python does this better than us.
63#
64# Revision 1.27  2003/11/23 19:01:36  jalet
65# Job price added to history
66#
67# Revision 1.26  2003/11/21 14:28:45  jalet
68# More complete job history.
69#
70# Revision 1.25  2003/10/08 21:12:27  jalet
71# Do not cache anymore entries which don't exist.
72#
73# Revision 1.24  2003/10/07 22:06:05  jalet
74# Preliminary code to disable job history
75#
76# Revision 1.23  2003/10/07 09:07:28  jalet
77# Character encoding added to please latest version of Python
78#
79# Revision 1.22  2003/10/06 13:12:27  jalet
80# More work on caching
81#
82# Revision 1.21  2003/10/03 09:02:20  jalet
83# Logs cache store actions too
84#
85# Revision 1.20  2003/10/02 20:23:18  jalet
86# Storage caching mechanism added.
87#
88# Revision 1.19  2003/07/16 21:53:07  jalet
89# Really big modifications wrt new configuration file's location and content.
90#
91# Revision 1.18  2003/07/07 08:33:18  jalet
92# Bug fix due to a typo in LDAP code
93#
94# Revision 1.17  2003/07/05 07:46:50  jalet
95# The previous bug fix was incomplete.
96#
97# Revision 1.16  2003/06/25 19:52:31  jalet
98# Should be ready for testing :-)
99#
100# Revision 1.15  2003/06/25 14:10:58  jalet
101# Exception raising for now.
102#
103# Revision 1.14  2003/06/25 14:10:01  jalet
104# Hey, it may work (edpykota --reset excepted) !
105#
106# Revision 1.13  2003/06/10 16:37:54  jalet
107# Deletion of the second user which is not needed anymore.
108# Added a debug configuration field in /etc/pykota.conf
109# All queries can now be sent to the logger in debug mode, this will
110# greatly help improve performance when time for this will come.
111#
112# Revision 1.12  2003/04/23 22:13:57  jalet
113# Preliminary support for LPRng added BUT STILL UNTESTED.
114#
115# Revision 1.11  2003/04/10 21:47:20  jalet
116# Job history added. Upgrade script neutralized for now !
117#
118# Revision 1.10  2003/03/29 13:45:27  jalet
119# GPL paragraphs were incorrectly (from memory) copied into the sources.
120# Two README files were added.
121# Upgrade script for PostgreSQL pre 1.01 schema was added.
122#
123# Revision 1.9  2003/02/17 22:55:01  jalet
124# More options can now be set per printer or globally :
125#
126#       admin
127#       adminmail
128#       gracedelay
129#       requester
130#
131# the printer option has priority when both are defined.
132#
133# Revision 1.8  2003/02/17 22:05:50  jalet
134# Storage backend now supports admin and user passwords (untested)
135#
136# Revision 1.7  2003/02/10 12:07:31  jalet
137# Now repykota should output the recorded total page number for each printer too.
138#
139# Revision 1.6  2003/02/09 13:05:43  jalet
140# Internationalization continues...
141#
142# Revision 1.5  2003/02/08 22:39:46  jalet
143# --reset command line option added
144#
145# Revision 1.4  2003/02/08 09:59:59  jalet
146# Added preliminary base class for all storages
147#
148# Revision 1.3  2003/02/05 22:10:29  jalet
149# Typos
150#
151# Revision 1.2  2003/02/05 22:02:22  jalet
152# __import__ statement didn't work as expected
153#
154# Revision 1.1  2003/02/05 21:28:17  jalet
155# Initial import into CVS
156#
157#
158#
159
160class PyKotaStorageError(Exception):
161    """An exception for Quota Storage related stuff."""
162    def __init__(self, message = ""):
163        self.message = message
164        Exception.__init__(self, message)
165    def __repr__(self):
166        return self.message
167    __str__ = __repr__
168       
169class StorageObject :
170    """Object present in the Quota Storage."""
171    def __init__(self, parent) :
172        "Initialize minimal data."""
173        self.parent = parent
174        self.ident = None
175        self.Exists = 0
176       
177class StorageUser(StorageObject) :       
178    """User class."""
179    def __init__(self, parent, name) :
180        StorageObject.__init__(self, parent)
181        self.Name = name
182        self.LimitBy = None
183        self.AccountBalance = None
184        self.LifeTimePaid = None
185        self.Email = None
186       
187    def consumeAccountBalance(self, amount) :     
188        """Consumes an amount of money from the user's account balance."""
189        self.parent.decreaseUserAccountBalance(self, amount)
190        self.AccountBalance = float(self.AccountBalance or 0.0) - amount
191       
192    def setAccountBalance(self, balance, lifetimepaid) :   
193        """Sets the user's account balance in case he pays more money."""
194        self.parent.writeUserAccountBalance(self, balance, lifetimepaid)
195        self.AccountBalance = balance
196        self.LifeTimePaid = lifetimepaid
197       
198    def setLimitBy(self, limitby) :   
199        """Sets the user's limiting factor."""
200        try :
201            limitby = limitby.lower()
202        except AttributeError :   
203            limitby = "quota"
204        if limitby in ["quota", "balance"] :
205            self.parent.writeUserLimitBy(self, limitby)
206            self.LimitBy = limitby
207       
208    def delete(self) :   
209        """Deletes an user from the Quota Storage."""
210        self.parent.beginTransaction()
211        try :
212            self.parent.deleteUser(self)
213        except PyKotaStorageError, msg :   
214            self.parent.rollbackTransaction()
215            raise PyKotaStorageError, msg
216        else :   
217            self.parent.commitTransaction()
218       
219class StorageGroup(StorageObject) :       
220    """User class."""
221    def __init__(self, parent, name) :
222        StorageObject.__init__(self, parent)
223        self.Name = name
224        self.LimitBy = None
225        self.AccountBalance = None
226        self.LifeTimePaid = None
227       
228    def setLimitBy(self, limitby) :   
229        """Sets the user's limiting factor."""
230        try :
231            limitby = limitby.lower()
232        except AttributeError :   
233            limitby = "quota"
234        if limitby in ["quota", "balance"] :
235            self.parent.writeGroupLimitBy(self, limitby)
236            self.LimitBy = limitby
237       
238    def delete(self) :   
239        """Deletes a group from the Quota Storage."""
240        self.parent.beginTransaction()
241        try :
242            self.parent.deleteGroup(self)
243        except PyKotaStorageError, msg :   
244            self.parent.rollbackTransaction()
245            raise PyKotaStorageError, msg
246        else :   
247            self.parent.commitTransaction()
248       
249class StoragePrinter(StorageObject) :
250    """Printer class."""
251    def __init__(self, parent, name) :
252        StorageObject.__init__(self, parent)
253        self.Name = name
254        self.PricePerPage = None
255        self.PricePerJob = None
256        self.LastJob = None
257       
258    def addJobToHistory(self, jobid, user, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None) :
259        """Adds a job to the printer's history."""
260        self.parent.writeJobNew(self, user, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options)
261        # TODO : update LastJob object ? Probably not needed.
262       
263    def addPrinterToGroup(self, printer) :   
264        """Adds a printer to a printer group."""
265        if (printer not in self.parent.getParentPrinters(self)) and (printer.ident != self.ident) :
266            self.parent.writePrinterToGroup(self, printer)
267            # TODO : reset cached value for printer parents, or add new parent to cached value
268           
269    def delPrinterFromGroup(self, printer) :   
270        """Deletes a printer from a printer group."""
271        if (printer in self.parent.getParentPrinters(self)) and (printer.ident != self.ident) :
272            self.parent.removePrinterFromGroup(self, printer)
273            # TODO : reset cached value for printer parents, or add new parent to cached value
274       
275    def setPrices(self, priceperpage = None, priceperjob = None) :   
276        """Sets the printer's prices."""
277        if priceperpage is None :
278            priceperpage = self.PricePerPage
279        else :   
280            self.PricePerPage = float(priceperpage)
281        if priceperjob is None :   
282            priceperjob = self.PricePerJob
283        else :   
284            self.PricePerJob = float(priceperjob)
285        self.parent.writePrinterPrices(self)
286       
287    def delete(self) :   
288        """Deletes a printer from the Quota Storage."""
289        self.parent.beginTransaction()
290        try :
291            self.parent.deletePrinter(self)
292        except PyKotaStorageError, msg :   
293            self.parent.rollbackTransaction()
294            raise PyKotaStorageError, msg
295        else :   
296            self.parent.commitTransaction()
297       
298class StorageUserPQuota(StorageObject) :
299    """User Print Quota class."""
300    def __init__(self, parent, user, printer) :
301        StorageObject.__init__(self, parent)
302        self.User = user
303        self.Printer = printer
304        self.PageCounter = None
305        self.LifePageCounter = None
306        self.SoftLimit = None
307        self.HardLimit = None
308        self.DateLimit = None
309        self.ParentPrintersUserPQuota = (user.Exists and printer.Exists and parent.getParentPrintersUserPQuota(self)) or []
310       
311    def setDateLimit(self, datelimit) :   
312        """Sets the date limit for this quota."""
313        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)
314        self.parent.writeUserPQuotaDateLimit(self, date)
315        self.DateLimit = date
316       
317    def setLimits(self, softlimit, hardlimit) :   
318        """Sets the soft and hard limit for this quota."""
319        self.parent.writeUserPQuotaLimits(self, softlimit, hardlimit)
320        self.SoftLimit = softlimit
321        self.HardLimit = hardlimit
322       
323    def reset(self) :   
324        """Resets page counter to 0."""
325        self.parent.writeUserPQuotaPagesCounters(self, 0, int(self.LifePageCounter or 0))
326        self.PageCounter = 0
327       
328    def computeJobPrice(self, jobsize) :   
329        """Computes the job price as the sum of all parent printers' prices + current printer's ones."""
330        totalprice = 0.0   
331        if jobsize :
332            for upq in [ self ] + self.ParentPrintersUserPQuota :
333                price = (float(upq.Printer.PricePerPage or 0.0) * jobsize) + float(upq.Printer.PricePerJob or 0.0)
334                totalprice += price
335        return totalprice   
336           
337    def increasePagesUsage(self, jobsize) :
338        """Increase the value of used pages and money."""
339        jobprice = self.computeJobPrice(jobsize)
340        if jobsize :
341            self.parent.beginTransaction()
342            try :
343                self.User.consumeAccountBalance(jobprice)
344                for upq in [ self ] + self.ParentPrintersUserPQuota :
345                    self.parent.increaseUserPQuotaPagesCounters(upq, jobsize)
346                    upq.PageCounter = int(upq.PageCounter or 0) + jobsize
347                    upq.LifePageCounter = int(upq.LifePageCounter or 0) + jobsize
348            except PyKotaStorageError, msg :   
349                self.parent.rollbackTransaction()
350                raise PyKotaStorageError, msg
351            else :   
352                self.parent.commitTransaction()
353        return jobprice
354       
355class StorageGroupPQuota(StorageObject) :
356    """Group Print Quota class."""
357    def __init__(self, parent, group, printer) :
358        StorageObject.__init__(self, parent)
359        self.Group = group
360        self.Printer = printer
361        self.PageCounter = None
362        self.LifePageCounter = None
363        self.SoftLimit = None
364        self.HardLimit = None
365        self.DateLimit = None
366       
367    def setDateLimit(self, datelimit) :   
368        """Sets the date limit for this quota."""
369        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)
370        self.parent.writeGroupPQuotaDateLimit(self, date)
371        self.DateLimit = date
372       
373    def setLimits(self, softlimit, hardlimit) :   
374        """Sets the soft and hard limit for this quota."""
375        self.parent.writeGroupPQuotaLimits(self, softlimit, hardlimit)
376        self.SoftLimit = softlimit
377        self.HardLimit = hardlimit
378       
379class StorageJob(StorageObject) :
380    """Printer's Last Job class."""
381    def __init__(self, parent) :
382        StorageObject.__init__(self, parent)
383        self.User = None
384        self.Printer = None
385        self.JobId = None
386        self.PrinterPageCounter = None
387        self.JobSize = None
388        self.JobAction = None
389        self.JobDate = None
390        self.JobPrice = None
391        self.JobFileName = None
392        self.JobTitle = None
393        self.JobCopies = None
394        self.JobOptions = None
395       
396class StorageLastJob(StorageJob) :
397    """Printer's Last Job class."""
398    def __init__(self, parent, printer) :
399        StorageJob.__init__(self, parent)
400        self.Printer = printer
401       
402    def setSize(self, jobsize) :
403        """Sets the last job's size."""
404        jobprice = self.parent.getUserPQuota(self.User, self.Printer).computeJobPrice(jobsize)
405        self.parent.writeLastJobSize(self, jobsize, jobprice)
406        self.JobSize = jobsize
407        self.JobPrice = jobprice
408       
409class BaseStorage :
410    def __init__(self, pykotatool) :
411        """Opens the LDAP connection."""
412        # raise PyKotaStorageError, "Sorry, the LDAP backend for PyKota is not yet implemented !"
413        self.closed = 1
414        self.tool = pykotatool
415        self.usecache = pykotatool.config.getCaching()
416        self.disablehistory = pykotatool.config.getDisableHistory()
417        if self.usecache :
418            self.tool.logdebug("Caching enabled.")
419            self.caches = { "USERS" : {}, "GROUPS" : {}, "PRINTERS" : {}, "USERPQUOTAS" : {}, "GROUPPQUOTAS" : {}, "JOBS" : {}, "LASTJOBS" : {} }
420       
421    def close(self) :   
422        """Must be overriden in children classes."""
423        raise RuntimeError, "BaseStorage.close() must be overriden !"
424       
425    def __del__(self) :       
426        """Ensures that the database connection is closed."""
427        self.close()
428       
429    def getFromCache(self, cachetype, key) :
430        """Tries to extract something from the cache."""
431        if self.usecache :
432            entry = self.caches[cachetype].get(key)
433            if entry is not None :
434                self.tool.logdebug("Cache hit (%s->%s)" % (cachetype, key))
435            else :   
436                self.tool.logdebug("Cache miss (%s->%s)" % (cachetype, key))
437            return entry   
438           
439    def cacheEntry(self, cachetype, key, value) :       
440        """Puts an entry in the cache."""
441        if self.usecache and getattr(value, "Exists", 0) :
442            self.caches[cachetype][key] = value
443            self.tool.logdebug("Cache store (%s->%s)" % (cachetype, key))
444           
445    def getUser(self, username) :       
446        """Returns the user from cache."""
447        user = self.getFromCache("USERS", username)
448        if user is None :
449            user = self.getUserFromBackend(username)
450            self.cacheEntry("USERS", username, user)
451        return user   
452       
453    def getGroup(self, groupname) :       
454        """Returns the group from cache."""
455        group = self.getFromCache("GROUPS", groupname)
456        if group is None :
457            group = self.getGroupFromBackend(groupname)
458            self.cacheEntry("GROUPS", groupname, group)
459        return group   
460       
461    def getPrinter(self, printername) :       
462        """Returns the printer from cache."""
463        printer = self.getFromCache("PRINTERS", printername)
464        if printer is None :
465            printer = self.getPrinterFromBackend(printername)
466            self.cacheEntry("PRINTERS", printername, printer)
467        return printer   
468       
469    def getUserPQuota(self, user, printer) :       
470        """Returns the user quota information from cache."""
471        useratprinter = "%s@%s" % (user.Name, printer.Name)
472        upquota = self.getFromCache("USERPQUOTAS", useratprinter)
473        if upquota is None :
474            upquota = self.getUserPQuotaFromBackend(user, printer)
475            self.cacheEntry("USERPQUOTAS", useratprinter, upquota)
476        return upquota   
477       
478    def getGroupPQuota(self, group, printer) :       
479        """Returns the group quota information from cache."""
480        groupatprinter = "%s@%s" % (group.Name, printer.Name)
481        gpquota = self.getFromCache("GROUPPQUOTAS", groupatprinter)
482        if gpquota is None :
483            gpquota = self.getGroupPQuotaFromBackend(group, printer)
484            self.cacheEntry("GROUPPQUOTAS", groupatprinter, gpquota)
485        return gpquota   
486       
487    def getPrinterLastJob(self, printer) :       
488        """Extracts last job information for a given printer from cache."""
489        lastjob = self.getFromCache("LASTJOBS", printer.Name)
490        if lastjob is None :
491            lastjob = self.getPrinterLastJobFromBackend(printer)
492            self.cacheEntry("LASTJOBS", printer.Name, lastjob)
493        return lastjob   
494       
495    def getParentPrinters(self, printer) :   
496        """Extracts parent printers information for a given printer from cache."""
497        if self.usecache :
498            if not hasattr(printer, "Parents") :
499                self.tool.logdebug("Cache miss (%s->Parents)" % printer.Name)
500                printer.Parents = self.getParentPrintersFromBackend(printer)
501                self.tool.logdebug("Cache store (%s->Parents)" % printer.Name)
502            else :
503                self.tool.logdebug("Cache hit (%s->Parents)" % printer.Name)
504        else :       
505            printer.Parents = self.getParentPrintersFromBackend(printer)
506        return printer.Parents
507       
508    def getGroupMembers(self, group) :       
509        """Returns the group's members list from in-group cache."""
510        if self.usecache :
511            if not hasattr(group, "Members") :
512                self.tool.logdebug("Cache miss (%s->Members)" % group.Name)
513                group.Members = self.getGroupMembersFromBackend(group)
514                self.tool.logdebug("Cache store (%s->Members)" % group.Name)
515            else :
516                self.tool.logdebug("Cache hit (%s->Members)" % group.Name)
517        else :       
518            group.Members = self.getGroupMembersFromBackend(group)
519        return group.Members   
520       
521    def getUserGroups(self, user) :       
522        """Returns the user's groups list from in-user cache."""
523        if self.usecache :
524            if not hasattr(user, "Groups") :
525                self.tool.logdebug("Cache miss (%s->Groups)" % user.Name)
526                user.Groups = self.getUserGroupsFromBackend(user)
527                self.tool.logdebug("Cache store (%s->Groups)" % user.Name)
528            else :
529                self.tool.logdebug("Cache hit (%s->Groups)" % user.Name)
530        else :       
531            user.Groups = self.getUserGroupsFromBackend(user)
532        return user.Groups   
533       
534    def getParentPrintersUserPQuota(self, userpquota) :     
535        """Returns all user print quota on the printer and its parents."""
536        upquotas = [ ]
537        for printer in self.getParentPrinters(userpquota.Printer) :
538            upquotas.append(self.getUserPQuota(userpquota.User, printer))
539        return upquotas       
540       
541def openConnection(pykotatool) :
542    """Returns a connection handle to the appropriate Quota Storage Database."""
543    backendinfo = pykotatool.config.getStorageBackend()
544    backend = backendinfo["storagebackend"]
545    try :
546        exec "from pykota.storages import %s as storagebackend" % backend.lower()
547    except ImportError :
548        raise PyKotaStorageError, _("Unsupported quota storage backend %s") % backend
549    else :   
550        host = backendinfo["storageserver"]
551        database = backendinfo["storagename"]
552        admin = backendinfo["storageadmin"] or backendinfo["storageuser"]
553        adminpw = backendinfo["storageadminpw"] or backendinfo["storageuserpw"]
554        return storagebackend.Storage(pykotatool, host, database, admin, adminpw)
Note: See TracBrowser for help on using the browser.