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

Revision 3056, 35.7 kB (checked in by jerome, 17 years ago)

The code to refund jobs is there and works (at least with PostgreSQL).
Only the pkrefund command line tool (and CGI script ?) is missing.

  • 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, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23#
24
25"""This module is the database abstraction layer for PyKota."""
26
27import os
28import imp
29from mx import DateTime
30
31class PyKotaStorageError(Exception):
32    """An exception for database related stuff."""
33    def __init__(self, message = ""):
34        self.message = message
35        Exception.__init__(self, message)
36    def __repr__(self):
37        return self.message
38    __str__ = __repr__
39       
40       
41class StorageObject :
42    """Object present in the database."""
43    def __init__(self, parent) :
44        "Initialize minimal data."""
45        self.parent = parent
46        self.ident = None
47        self.Description = None
48        self.isDirty = False
49        self.Exists = False
50       
51    def setDescription(self, description=None) :
52        """Sets the object's description."""
53        if description is not None :
54            self.Description = str(description)
55            self.isDirty = True   
56           
57    def save(self) :       
58        """Saves the object to the database."""
59        if self.isDirty :
60            getattr(self.parent, "save%s" % self.__class__.__name__[7:])(self)
61            self.isDirty = False
62           
63       
64class StorageUser(StorageObject) :       
65    """User class."""
66    def __init__(self, parent, name) :
67        StorageObject.__init__(self, parent)
68        self.Name = name
69        self.LimitBy = None
70        self.AccountBalance = None
71        self.LifeTimePaid = None
72        self.Email = None
73        self.OverCharge = 1.0
74        self.Payments = [] # TODO : maybe handle this smartly for SQL, for now just don't retrieve them
75        self.PaymentsBacklog = []
76       
77    def consumeAccountBalance(self, amount) :     
78        """Consumes an amount of money from the user's account balance."""
79        self.parent.decreaseUserAccountBalance(self, amount)
80        self.AccountBalance = float(self.AccountBalance or 0.0) - amount
81       
82    def setAccountBalance(self, balance, lifetimepaid, comment="") :
83        """Sets the user's account balance in case he pays more money."""
84        diff = float(lifetimepaid or 0.0) - float(self.LifeTimePaid or 0.0)
85        self.AccountBalance = balance
86        self.LifeTimePaid = lifetimepaid
87        if diff :
88            self.PaymentsBacklog.append((diff, comment))
89        self.isDirty = True
90       
91    def save(self) :   
92        """Saves an user and flush its payments backlog."""
93        for (value, comment) in self.PaymentsBacklog :
94            self.parent.writeNewPayment(self, value, comment)
95        self.PaymentsBacklog = []   
96        StorageObject.save(self)   
97       
98    def setLimitBy(self, limitby) :   
99        """Sets the user's limiting factor."""
100        try :
101            limitby = limitby.lower()
102        except AttributeError :   
103            limitby = "quota"
104        if limitby in ["quota", "balance", \
105                       "noquota", "noprint", "nochange"] :
106            self.LimitBy = limitby
107            self.isDirty = True
108       
109    def setOverChargeFactor(self, factor) :   
110        """Sets the user's overcharging coefficient."""
111        self.OverCharge = factor
112        self.isDirty = True
113       
114    def setEmail(self, email) :   
115        """Sets the user's email address."""
116        self.Email = email
117        self.isDirty = True
118       
119    def delete(self) :   
120        """Deletes an user from the database."""
121        self.parent.deleteUser(self)
122        self.parent.flushEntry("USERS", self.Name)
123        if self.parent.usecache :
124            for (k, v) in self.parent.caches["USERPQUOTAS"].items() :
125                if v.User.Name == self.Name :
126                    self.parent.flushEntry("USERPQUOTAS", "%s@%s" % (v.User.Name, v.Printer.Name))
127        self.Exists = False
128        self.isDirty = False           
129       
130    def refund(self, amount) :
131        """Refunds a number of credits to an user."""
132        self.consumeAccountBalance(-amount)
133       
134       
135class StorageGroup(StorageObject) :       
136    """User class."""
137    def __init__(self, parent, name) :
138        StorageObject.__init__(self, parent)
139        self.Name = name
140        self.LimitBy = None
141        self.AccountBalance = None
142        self.LifeTimePaid = None
143       
144    def setLimitBy(self, limitby) :   
145        """Sets the user's limiting factor."""
146        try :
147            limitby = limitby.lower()
148        except AttributeError :   
149            limitby = "quota"
150        if limitby in ["quota", "balance", "noquota"] :
151            self.LimitBy = limitby
152            self.isDirty = True
153       
154    def addUserToGroup(self, user) :
155        """Adds an user to an users group."""
156        self.parent.addUserToGroup(user, self)
157       
158    def delUserFromGroup(self, user) :
159        """Removes an user from an users group."""
160        self.parent.delUserFromGroup(user, self)
161       
162    def delete(self) :   
163        """Deletes a group from the database."""
164        self.parent.deleteGroup(self)
165        self.parent.flushEntry("GROUPS", self.Name)
166        if self.parent.usecache :
167            for (k, v) in self.parent.caches["GROUPPQUOTAS"].items() :
168                if v.Group.Name == self.Name :
169                    self.parent.flushEntry("GROUPPQUOTAS", "%s@%s" % (v.Group.Name, v.Printer.Name))
170        self.Exists = False
171        self.isDirty = False           
172       
173       
174class StoragePrinter(StorageObject) :
175    """Printer class."""
176    def __init__(self, parent, name) :
177        StorageObject.__init__(self, parent)
178        self.Name = name
179        self.PricePerPage = None
180        self.PricePerJob = None
181        self.MaxJobSize = None
182        self.PassThrough = None
183       
184    def __getattr__(self, name) :   
185        """Delays data retrieval until it's really needed."""
186        if name == "LastJob" : 
187            self.LastJob = self.parent.getPrinterLastJob(self)
188            self.parent.tool.logdebug("Lazy retrieval of last job for printer %s" % self.Name)
189            return self.LastJob
190        elif name == "Coefficients" :   
191            self.Coefficients = self.parent.tool.config.getPrinterCoefficients(self.Name)
192            self.parent.tool.logdebug("Lazy retrieval of coefficients for printer %s : %s" % (self.Name, self.Coefficients))
193            return self.Coefficients
194        else :
195            raise AttributeError, name
196           
197    def addJobToHistory(self, jobid, user, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None, precomputedsize=None, precomputedprice=None) :
198        """Adds a job to the printer's history."""
199        self.parent.writeJobNew(self, user, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, clienthost, jobsizebytes, jobmd5sum, jobpages, jobbilling, precomputedsize, precomputedprice)
200        # TODO : update LastJob object ? Probably not needed.
201       
202    def addPrinterToGroup(self, printer) :   
203        """Adds a printer to a printer group."""
204        if (printer not in self.parent.getParentPrinters(self)) and (printer.ident != self.ident) :
205            self.parent.writePrinterToGroup(self, printer)
206            # TODO : reset cached value for printer parents, or add new parent to cached value
207           
208    def delPrinterFromGroup(self, printer) :   
209        """Deletes a printer from a printer group."""
210        self.parent.removePrinterFromGroup(self, printer)
211        # TODO : reset cached value for printer parents, or add new parent to cached value
212       
213    def setPrices(self, priceperpage = None, priceperjob = None) :   
214        """Sets the printer's prices."""
215        if priceperpage is None :
216            priceperpage = self.PricePerPage or 0.0
217        else :   
218            self.PricePerPage = float(priceperpage)
219        if priceperjob is None :   
220            priceperjob = self.PricePerJob or 0.0
221        else :   
222            self.PricePerJob = float(priceperjob)
223        self.isDirty = True   
224       
225    def setPassThrough(self, passthrough) :
226        """Sets the printer's passthrough mode."""
227        self.PassThrough = passthrough
228        self.isDirty = True
229       
230    def setMaxJobSize(self, maxjobsize) :
231        """Sets the printer's maximal job size."""
232        self.MaxJobSize = maxjobsize
233        self.isDirty = True
234       
235    def delete(self) :   
236        """Deletes a printer from the database."""
237        self.parent.deletePrinter(self)
238        self.parent.flushEntry("PRINTERS", self.Name)
239        if self.parent.usecache :
240            for (k, v) in self.parent.caches["USERPQUOTAS"].items() :
241                if v.Printer.Name == self.Name :
242                    self.parent.flushEntry("USERPQUOTAS", "%s@%s" % (v.User.Name, v.Printer.Name))
243            for (k, v) in self.parent.caches["GROUPPQUOTAS"].items() :
244                if v.Printer.Name == self.Name :
245                    self.parent.flushEntry("GROUPPQUOTAS", "%s@%s" % (v.Group.Name, v.Printer.Name))
246        self.Exists = False
247        self.isDirty = False           
248       
249       
250class StorageUserPQuota(StorageObject) :
251    """User Print Quota class."""
252    def __init__(self, parent, user, printer) :
253        StorageObject.__init__(self, parent)
254        self.User = user
255        self.Printer = printer
256        self.PageCounter = None
257        self.LifePageCounter = None
258        self.SoftLimit = None
259        self.HardLimit = None
260        self.DateLimit = None
261        self.WarnCount = None
262        self.MaxJobSize = None
263       
264    def __getattr__(self, name) :   
265        """Delays data retrieval until it's really needed."""
266        if name == "ParentPrintersUserPQuota" : 
267            self.ParentPrintersUserPQuota = (self.User.Exists and self.Printer.Exists and self.parent.getParentPrintersUserPQuota(self)) or []
268            return self.ParentPrintersUserPQuota
269        else :
270            raise AttributeError, name
271       
272    def setDateLimit(self, datelimit) :   
273        """Sets the date limit for this quota."""
274        datelimit = DateTime.ISO.ParseDateTime(str(datelimit)[:19])
275        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)
276        self.parent.writeUserPQuotaDateLimit(self, date)
277        self.DateLimit = date
278       
279    def setLimits(self, softlimit, hardlimit) :   
280        """Sets the soft and hard limit for this quota."""
281        self.SoftLimit = softlimit
282        self.HardLimit = hardlimit
283        self.DateLimit = None
284        self.WarnCount = 0
285        self.isDirty = True
286       
287    def setUsage(self, used) :
288        """Sets the PageCounter and LifePageCounter to used, or if used is + or - prefixed, changes the values of {Life,}PageCounter by that amount."""
289        vused = int(used)
290        if used.startswith("+") or used.startswith("-") :
291            self.PageCounter += vused
292            self.LifePageCounter += vused
293        else :
294            self.PageCounter = self.LifePageCounter = vused
295        self.DateLimit = None
296        self.WarnCount = 0
297        self.isDirty = 1
298
299    def incDenyBannerCounter(self) :
300        """Increment the deny banner counter for this user quota."""
301        self.parent.increaseUserPQuotaWarnCount(self)
302        self.WarnCount = (self.WarnCount or 0) + 1
303       
304    def resetDenyBannerCounter(self) :
305        """Resets the deny banner counter for this user quota."""
306        self.parent.writeUserPQuotaWarnCount(self, 0)
307        self.WarnCount = 0
308       
309    def reset(self) :   
310        """Resets page counter to 0."""
311        self.PageCounter = 0
312        self.DateLimit = None
313        self.isDirty = True
314       
315    def hardreset(self) :   
316        """Resets actual and life time page counters to 0."""
317        self.PageCounter = self.LifePageCounter = 0
318        self.DateLimit = None
319        self.isDirty = True
320       
321    def computeJobPrice(self, jobsize, inkusage=[]) :   
322        """Computes the job price as the sum of all parent printers' prices + current printer's ones."""
323        totalprice = 0.0   
324        if jobsize :
325            if self.User.OverCharge != 0.0 :    # optimization, but TODO : beware of rounding errors
326                for upq in [ self ] + self.ParentPrintersUserPQuota :
327                    totalprice += float(upq.Printer.PricePerJob or 0.0)
328                    pageprice = float(upq.Printer.PricePerPage or 0.0)
329                    if not inkusage :
330                        totalprice += (jobsize * pageprice)
331                    else :   
332                        for pageindex in range(jobsize) :
333                            try :
334                                usage = inkusage[pageindex]
335                            except IndexError :   
336                                self.parent.tool.logdebug("No ink usage information. Using base cost of %f credits for page %i." % (pageprice, pageindex+1))
337                                totalprice += pageprice
338                            else :   
339                                coefficients = self.Printer.Coefficients
340                                for (ink, value) in usage.items() :
341                                    coefvalue = coefficients.get(ink, 1.0)
342                                    coefprice = (coefvalue * pageprice) / 100.0
343                                    inkprice = coefprice * value
344                                    self.parent.tool.logdebug("Applying coefficient %f for color %s (used at %f%% on page %i) to base cost %f gives %f" % (coefvalue, ink, value, pageindex+1, pageprice, inkprice))
345                                    totalprice += coefprice
346        if self.User.OverCharge != 1.0 : # TODO : beware of rounding errors
347            overcharged = totalprice * self.User.OverCharge       
348            self.parent.tool.logdebug("Overcharging %s by a factor of %s ===> User %s will be charged for %s credits." % (totalprice, self.User.OverCharge, self.User.Name, overcharged))
349            return overcharged
350        else :   
351            return totalprice
352           
353    def increasePagesUsage(self, jobsize, inkusage=[]) :
354        """Increase the value of used pages and money."""
355        jobprice = self.computeJobPrice(jobsize, inkusage)
356        if jobsize :
357            if jobprice :
358                self.User.consumeAccountBalance(jobprice)
359            for upq in [ self ] + self.ParentPrintersUserPQuota :
360                self.parent.increaseUserPQuotaPagesCounters(upq, jobsize)
361                upq.PageCounter = int(upq.PageCounter or 0) + jobsize
362                upq.LifePageCounter = int(upq.LifePageCounter or 0) + jobsize
363        return jobprice
364       
365    def delete(self) :   
366        """Deletes an user print quota entry from the database."""
367        self.parent.deleteUserPQuota(self)
368        if self.parent.usecache :
369            self.parent.flushEntry("USERPQUOTAS", "%s@%s" % (self.User.Name, self.Printer.Name))
370        self.Exists = False
371        self.isDirty = False
372       
373    def refund(self, nbpages) :   
374        """Refunds a number of pages to an user on a particular printer."""
375        self.parent.increaseUserPQuotaPagesCounters(self, -nbpages)
376        self.PageCounter = int(self.PageCounter or 0) - nbpages
377        self.LifePageCounter = int(self.LifePageCounter or 0) - nbpages
378       
379       
380class StorageGroupPQuota(StorageObject) :
381    """Group Print Quota class."""
382    def __init__(self, parent, group, printer) :
383        StorageObject.__init__(self, parent)
384        self.Group = group
385        self.Printer = printer
386        self.PageCounter = None
387        self.LifePageCounter = None
388        self.SoftLimit = None
389        self.HardLimit = None
390        self.DateLimit = None
391        self.MaxJobSize = None
392       
393    def __getattr__(self, name) :   
394        """Delays data retrieval until it's really needed."""
395        if name == "ParentPrintersGroupPQuota" : 
396            self.ParentPrintersGroupPQuota = (self.Group.Exists and self.Printer.Exists and self.parent.getParentPrintersGroupPQuota(self)) or []
397            return self.ParentPrintersGroupPQuota
398        else :
399            raise AttributeError, name
400       
401    def reset(self) :   
402        """Resets page counter to 0."""
403        for user in self.parent.getGroupMembers(self.Group) :
404            uq = self.parent.getUserPQuota(user, self.Printer)
405            uq.reset()
406            uq.save()
407        self.PageCounter = 0
408        self.DateLimit = None
409        self.isDirty = True
410       
411    def hardreset(self) :   
412        """Resets actual and life time page counters to 0."""
413        for user in self.parent.getGroupMembers(self.Group) :
414            uq = self.parent.getUserPQuota(user, self.Printer)
415            uq.hardreset()
416            uq.save()
417        self.PageCounter = self.LifePageCounter = 0
418        self.DateLimit = None
419        self.isDirty = True
420       
421    def setDateLimit(self, datelimit) :   
422        """Sets the date limit for this quota."""
423        datelimit = DateTime.ISO.ParseDateTime(str(datelimit)[:19])
424        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, \
425                                                  datelimit.month, \
426                                                  datelimit.day, \
427                                                  datelimit.hour, \
428                                                  datelimit.minute, \
429                                                  datelimit.second)
430        self.parent.writeGroupPQuotaDateLimit(self, date)
431        self.DateLimit = date
432       
433    def setLimits(self, softlimit, hardlimit) :   
434        """Sets the soft and hard limit for this quota."""
435        self.SoftLimit = softlimit
436        self.HardLimit = hardlimit
437        self.DateLimit = None
438        self.isDirty = True
439       
440    def delete(self) :   
441        """Deletes a group print quota entry from the database."""
442        self.parent.deleteGroupPQuota(self)
443        if self.parent.usecache :
444            self.parent.flushEntry("GROUPPQUOTAS", "%s@%s" % (self.Group.Name, self.Printer.Name))
445        self.Exists = False
446        self.isDirty = False
447       
448       
449class StorageJob(StorageObject) :
450    """Printer's Job class."""
451    def __init__(self, parent) :
452        StorageObject.__init__(self, parent)
453        self.UserName = None
454        self.PrinterName = None
455        self.JobId = None
456        self.PrinterPageCounter = None
457        self.JobSizeBytes = None
458        self.JobSize = None
459        self.JobAction = None
460        self.JobDate = None
461        self.JobPrice = None
462        self.JobFileName = None
463        self.JobTitle = None
464        self.JobCopies = None
465        self.JobOptions = None
466        self.JobHostName = None
467        self.JobMD5Sum = None
468        self.JobPages = None
469        self.JobBillingCode = None
470        self.PrecomputedJobSize = None
471        self.PrecomputedJobPrice = None
472       
473    def __getattr__(self, name) :   
474        """Delays data retrieval until it's really needed."""
475        if name == "User" : 
476            self.User = self.parent.getUser(self.UserName)
477            return self.User
478        elif name == "Printer" :   
479            self.Printer = self.parent.getPrinter(self.PrinterName)
480            return self.Printer
481        else :
482            raise AttributeError, name
483           
484    def refund(self) :       
485        """Refund a particular print job."""
486        if (not self.JobSize) or (self.JobAction in ("DENY", "REFUND")) :
487            return
488        self.parent.beginTransaction()
489        try :
490            if self.JobBillingCode :
491                bcode = self.parent.getBillingCode(self.JobBillingCode)
492                bcode.refund(self.JobSize, self.JobPrice)
493               
494            if self.User.Exists :
495                self.User.refund(self.JobPrice)
496                if self.Printer.Exists :   
497                    upq = self.parent.getUserPQuota(self.User, self.Printer)   
498                    if upq.Exists :
499                        upq.refund(self.JobSize)
500            self.parent.refundJob(self.ident)
501        except :       
502            self.parent.rollbackTransaction()
503            raise
504        else :   
505            self.parent.commitTransaction()
506       
507       
508class StorageLastJob(StorageJob) :
509    """Printer's Last Job class."""
510    def __init__(self, parent, printer) :
511        StorageJob.__init__(self, parent)
512        self.PrinterName = printer.Name # not needed
513        self.Printer = printer
514       
515       
516class StorageBillingCode(StorageObject) :
517    """Billing code class."""
518    def __init__(self, parent, name) :
519        StorageObject.__init__(self, parent)
520        self.BillingCode = name
521        self.PageCounter = None
522        self.Balance = None
523       
524    def delete(self) :   
525        """Deletes the billing code from the database."""
526        self.parent.deleteBillingCode(self)
527        self.parent.flushEntry("BILLINGCODES", self.BillingCode)
528        self.isDirty = False
529        self.Exists = False
530       
531    def reset(self, balance=0.0, pagecounter=0) :   
532        """Resets the pagecounter and balance for this billing code."""
533        self.Balance = balance
534        self.PageCounter = pagecounter
535        self.isDirty = True
536       
537    def consume(self, pages, price) :
538        """Consumes some pages and credits for this billing code."""
539        if pages :
540            self.parent.consumeBillingCode(self, pages, price)
541            self.PageCounter += pages
542            self.Balance -= price
543           
544    def refund(self, pages, price) :
545        """Refunds a particular billing code."""
546        self.consume(-pages, -price)
547       
548       
549class BaseStorage :
550    def __init__(self, pykotatool) :
551        """Opens the storage connection."""
552        self.closed = 1
553        self.tool = pykotatool
554        self.usecache = pykotatool.config.getCaching()
555        self.disablehistory = pykotatool.config.getDisableHistory()
556        self.privacy = pykotatool.config.getPrivacy()
557        if self.privacy :
558            pykotatool.logdebug("Jobs' title, filename and options will be hidden because of privacy concerns.")
559        if self.usecache :
560            self.tool.logdebug("Caching enabled.")
561            self.caches = { "USERS" : {}, \
562                            "GROUPS" : {}, \
563                            "PRINTERS" : {}, \
564                            "USERPQUOTAS" : {}, \
565                            "GROUPPQUOTAS" : {}, \
566                            "JOBS" : {}, \
567                            "LASTJOBS" : {}, \
568                            "BILLINGCODES" : {} }
569       
570    def close(self) :   
571        """Must be overriden in children classes."""
572        raise RuntimeError, "BaseStorage.close() must be overriden !"
573       
574    def __del__(self) :       
575        """Ensures that the database connection is closed."""
576        self.close()
577       
578    def getFromCache(self, cachetype, key) :
579        """Tries to extract something from the cache."""
580        if self.usecache :
581            entry = self.caches[cachetype].get(key)
582            if entry is not None :
583                self.tool.logdebug("Cache hit (%s->%s)" % (cachetype, key))
584            else :   
585                self.tool.logdebug("Cache miss (%s->%s)" % (cachetype, key))
586            return entry   
587           
588    def cacheEntry(self, cachetype, key, value) :       
589        """Puts an entry in the cache."""
590        if self.usecache and getattr(value, "Exists", 0) :
591            self.caches[cachetype][key] = value
592            self.tool.logdebug("Cache store (%s->%s)" % (cachetype, key))
593           
594    def flushEntry(self, cachetype, key) :       
595        """Removes an entry from the cache."""
596        if self.usecache :
597            try :
598                del self.caches[cachetype][key]
599            except KeyError :   
600                pass
601            else :   
602                self.tool.logdebug("Cache flush (%s->%s)" % (cachetype, key))
603           
604    def getUser(self, username) :       
605        """Returns the user from cache."""
606        user = self.getFromCache("USERS", username)
607        if user is None :
608            user = self.getUserFromBackend(username)
609            self.cacheEntry("USERS", username, user)
610        return user   
611       
612    def getGroup(self, groupname) :       
613        """Returns the group from cache."""
614        group = self.getFromCache("GROUPS", groupname)
615        if group is None :
616            group = self.getGroupFromBackend(groupname)
617            self.cacheEntry("GROUPS", groupname, group)
618        return group   
619       
620    def getPrinter(self, printername) :       
621        """Returns the printer from cache."""
622        printer = self.getFromCache("PRINTERS", printername)
623        if printer is None :
624            printer = self.getPrinterFromBackend(printername)
625            self.cacheEntry("PRINTERS", printername, printer)
626        return printer   
627       
628    def getUserPQuota(self, user, printer) :       
629        """Returns the user quota information from cache."""
630        useratprinter = "%s@%s" % (user.Name, printer.Name)
631        upquota = self.getFromCache("USERPQUOTAS", useratprinter)
632        if upquota is None :
633            upquota = self.getUserPQuotaFromBackend(user, printer)
634            self.cacheEntry("USERPQUOTAS", useratprinter, upquota)
635        return upquota   
636       
637    def getGroupPQuota(self, group, printer) :       
638        """Returns the group quota information from cache."""
639        groupatprinter = "%s@%s" % (group.Name, printer.Name)
640        gpquota = self.getFromCache("GROUPPQUOTAS", groupatprinter)
641        if gpquota is None :
642            gpquota = self.getGroupPQuotaFromBackend(group, printer)
643            self.cacheEntry("GROUPPQUOTAS", groupatprinter, gpquota)
644        return gpquota   
645       
646    def getPrinterLastJob(self, printer) :       
647        """Extracts last job information for a given printer from cache."""
648        lastjob = self.getFromCache("LASTJOBS", printer.Name)
649        if lastjob is None :
650            lastjob = self.getPrinterLastJobFromBackend(printer)
651            self.cacheEntry("LASTJOBS", printer.Name, lastjob)
652        return lastjob   
653       
654    def getBillingCode(self, label) :       
655        """Returns the user from cache."""
656        code = self.getFromCache("BILLINGCODES", label)
657        if code is None :
658            code = self.getBillingCodeFromBackend(label)
659            self.cacheEntry("BILLINGCODES", label, code)
660        return code
661       
662    def getParentPrinters(self, printer) :   
663        """Extracts parent printers information for a given printer from cache."""
664        if self.usecache :
665            if not hasattr(printer, "Parents") :
666                self.tool.logdebug("Cache miss (%s->Parents)" % printer.Name)
667                printer.Parents = self.getParentPrintersFromBackend(printer)
668                self.tool.logdebug("Cache store (%s->Parents)" % printer.Name)
669            else :
670                self.tool.logdebug("Cache hit (%s->Parents)" % printer.Name)
671        else :       
672            printer.Parents = self.getParentPrintersFromBackend(printer)
673        for parent in printer.Parents[:] :   
674            printer.Parents.extend(self.getParentPrinters(parent))
675        uniquedic = {}   
676        for parent in printer.Parents :
677            uniquedic[parent.Name] = parent
678        printer.Parents = uniquedic.values()   
679        return printer.Parents
680       
681    def getGroupMembers(self, group) :       
682        """Returns the group's members list from in-group cache."""
683        if self.usecache :
684            if not hasattr(group, "Members") :
685                self.tool.logdebug("Cache miss (%s->Members)" % group.Name)
686                group.Members = self.getGroupMembersFromBackend(group)
687                self.tool.logdebug("Cache store (%s->Members)" % group.Name)
688            else :
689                self.tool.logdebug("Cache hit (%s->Members)" % group.Name)
690        else :       
691            group.Members = self.getGroupMembersFromBackend(group)
692        return group.Members   
693       
694    def getUserGroups(self, user) :       
695        """Returns the user's groups list from in-user cache."""
696        if self.usecache :
697            if not hasattr(user, "Groups") :
698                self.tool.logdebug("Cache miss (%s->Groups)" % user.Name)
699                user.Groups = self.getUserGroupsFromBackend(user)
700                self.tool.logdebug("Cache store (%s->Groups)" % user.Name)
701            else :
702                self.tool.logdebug("Cache hit (%s->Groups)" % user.Name)
703        else :       
704            user.Groups = self.getUserGroupsFromBackend(user)
705        return user.Groups   
706       
707    def getParentPrintersUserPQuota(self, userpquota) :     
708        """Returns all user print quota on the printer and all its parents recursively."""
709        upquotas = [ ]
710        for printer in self.getParentPrinters(userpquota.Printer) :
711            upq = self.getUserPQuota(userpquota.User, printer)
712            if upq.Exists :
713                upquotas.append(upq)
714        return upquotas       
715       
716    def getParentPrintersGroupPQuota(self, grouppquota) :     
717        """Returns all group print quota on the printer and all its parents recursively."""
718        gpquotas = [ ]
719        for printer in self.getParentPrinters(grouppquota.Printer) :
720            gpq = self.getGroupPQuota(grouppquota.Group, printer)
721            if gpq.Exists :
722                gpquotas.append(gpq)
723        return gpquotas       
724       
725    def databaseToUserCharset(self, text) :
726        """Converts from database format (UTF-8) to user's charset."""
727        return self.tool.UTF8ToUserCharset(text)
728       
729    def userCharsetToDatabase(self, text) :
730        """Converts from user's charset to database format (UTF-8)."""
731        return self.tool.userCharsetToUTF8(text)
732       
733    def cleanDates(self, startdate, enddate) :   
734        """Clean the dates to create a correct filter."""
735        if startdate :   
736            startdate = startdate.strip().lower()
737        if enddate :   
738            enddate = enddate.strip().lower()
739        if (not startdate) and (not enddate) :   
740            return (None, None)
741           
742        now = DateTime.now()   
743        nameddates = ('yesterday', 'today', 'now', 'tomorrow')
744        datedict = { "start" : startdate, "end" : enddate }   
745        for limit in datedict.keys() :
746            dateval = datedict[limit]
747            if dateval :
748                for name in nameddates :
749                    if dateval.startswith(name) :
750                        try :
751                            offset = int(dateval[len(name):])
752                        except :   
753                            offset = 0
754                        dateval = dateval[:len(name)]   
755                        if limit == "start" :
756                            if dateval == "yesterday" :
757                                dateval = (now - 1 + offset).Format("%Y%m%d000000")
758                            elif dateval == "today" :
759                                dateval = (now + offset).Format("%Y%m%d000000")
760                            elif dateval == "now" :
761                                dateval = (now + offset).Format("%Y%m%d%H%M%S")
762                            else : # tomorrow
763                                dateval = (now + 1 + offset).Format("%Y%m%d000000")
764                        else :
765                            if dateval == "yesterday" :
766                                dateval = (now - 1 + offset).Format("%Y%m%d235959")
767                            elif dateval == "today" :
768                                dateval = (now + offset).Format("%Y%m%d235959")
769                            elif dateval == "now" :
770                                dateval = (now + offset).Format("%Y%m%d%H%M%S")
771                            else : # tomorrow
772                                dateval = (now + 1 + offset).Format("%Y%m%d235959")
773                        break
774                       
775                if not dateval.isdigit() :
776                    dateval = None
777                else :   
778                    lgdateval = len(dateval)
779                    if lgdateval == 4 :
780                        if limit == "start" : 
781                            dateval = "%s0101 00:00:00" % dateval
782                        else : 
783                            dateval = "%s1231 23:59:59" % dateval
784                    elif lgdateval == 6 :
785                        if limit == "start" : 
786                            dateval = "%s01 00:00:00" % dateval
787                        else : 
788                            mxdate = DateTime.ISO.ParseDateTime("%s01 00:00:00" % dateval)
789                            dateval = "%s%02i 23:59:59" % (dateval, mxdate.days_in_month)
790                    elif lgdateval == 8 :
791                        if limit == "start" : 
792                            dateval = "%s 00:00:00" % dateval
793                        else : 
794                            dateval = "%s 23:59:59" % dateval
795                    elif lgdateval == 10 :
796                        if limit == "start" : 
797                            dateval = "%s %s:00:00" % (dateval[:8], dateval[8:])
798                        else : 
799                            dateval = "%s %s:59:59" % (dateval[:8], dateval[8:])
800                    elif lgdateval == 12 :
801                        if limit == "start" : 
802                            dateval = "%s %s:%s:00" % (dateval[:8], dateval[8:10], dateval[10:])
803                        else : 
804                            dateval = "%s %s:%s:59" % (dateval[:8], dateval[8:10], dateval[10:])
805                    elif lgdateval == 14 :       
806                        dateval = "%s %s:%s:%s" % (dateval[:8], dateval[8:10], dateval[10:12], dateval[12:])
807                    else :   
808                        dateval = None
809                    try :   
810                        DateTime.ISO.ParseDateTime(dateval[:19])
811                    except :   
812                        dateval = None
813                datedict[limit] = dateval   
814        (start, end) = (datedict["start"], datedict["end"])
815        if start and end and (start > end) :
816            (start, end) = (end, start)
817        try :   
818            if len(start) == 17 :
819                start = "%s-%s-%s %s" % (start[0:4], start[4:6], start[6:8], start[9:])
820        except TypeError :       
821            pass
822           
823        try :   
824            if len(end) == 17 :
825                end = "%s-%s-%s %s" % (end[0:4], end[4:6], end[6:8], end[9:])
826        except TypeError :       
827            pass
828           
829        return (start, end)   
830       
831def openConnection(pykotatool) :
832    """Returns a connection handle to the appropriate database."""
833    backendinfo = pykotatool.config.getStorageBackend()
834    backend = backendinfo["storagebackend"]
835    try :
836        storagebackend = imp.load_source("storagebackend", 
837                                         os.path.join(os.path.dirname(__file__),
838                                                      "storages",
839                                                      "%s.py" % backend.lower()))
840    except ImportError :
841        raise PyKotaStorageError, _("Unsupported quota storage backend %s") % backend
842    else :   
843        host = backendinfo["storageserver"]
844        database = backendinfo["storagename"]
845        admin = backendinfo["storageadmin"] or backendinfo["storageuser"]
846        adminpw = backendinfo["storageadminpw"] or backendinfo["storageuserpw"]
847        return storagebackend.Storage(pykotatool, host, database, admin, adminpw)
Note: See TracBrowser for help on using the browser.