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

Revision 3245, 36.5 kB (checked in by jerome, 17 years ago)

Now the pykotme command line tool computes size and price based on the preaccounter
define for each printer instead of always using the internal page counter.
IMPORTANT : pykotme.cgi still uses the old behavior.

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