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
RevLine 
[695]1# PyKota
[1144]2# -*- coding: ISO-8859-15 -*-
[695]3#
[952]4# PyKota : Print Quotas for CUPS and LPRng
[695]5#
[3133]6# (c) 2003, 2004, 2005, 2006, 2007 Jerome Alet <alet@librelogiciel.com>
[873]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.
[695]11#
[873]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
[2302]19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
[695]20#
21# $Id$
22#
[2066]23#
[695]24
[3056]25"""This module is the database abstraction layer for PyKota."""
26
[2945]27import os
28import imp
[2266]29from mx import DateTime
30
[695]31class PyKotaStorageError(Exception):
[2717]32    """An exception for database related stuff."""
[695]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__
[759]39       
[3056]40       
[1041]41class StorageObject :
[2717]42    """Object present in the database."""
[1041]43    def __init__(self, parent) :
44        "Initialize minimal data."""
45        self.parent = parent
46        self.ident = None
[2697]47        self.Description = None
[2676]48        self.isDirty = False
49        self.Exists = False
[1041]50       
[2696]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   
[2699]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
[3056]62           
[2696]63       
[1041]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
[1067]72        self.Email = None
[2054]73        self.OverCharge = 1.0
[1522]74        self.Payments = [] # TODO : maybe handle this smartly for SQL, for now just don't retrieve them
[2773]75        self.PaymentsBacklog = []
[1041]76       
77    def consumeAccountBalance(self, amount) :     
78        """Consumes an amount of money from the user's account balance."""
[1269]79        self.parent.decreaseUserAccountBalance(self, amount)
80        self.AccountBalance = float(self.AccountBalance or 0.0) - amount
[1041]81       
[2452]82    def setAccountBalance(self, balance, lifetimepaid, comment="") :
[1041]83        """Sets the user's account balance in case he pays more money."""
[1522]84        diff = float(lifetimepaid or 0.0) - float(self.LifeTimePaid or 0.0)
[2707]85        self.AccountBalance = balance
86        self.LifeTimePaid = lifetimepaid
[2773]87        if diff :
88            self.PaymentsBacklog.append((diff, comment))
[2707]89        self.isDirty = True
[1041]90       
[2773]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       
[1041]98    def setLimitBy(self, limitby) :   
99        """Sets the user's limiting factor."""
[1062]100        try :
101            limitby = limitby.lower()
102        except AttributeError :   
103            limitby = "quota"
[2452]104        if limitby in ["quota", "balance", \
105                       "noquota", "noprint", "nochange"] :
[1041]106            self.LimitBy = limitby
[2706]107            self.isDirty = True
[1041]108       
[2054]109    def setOverChargeFactor(self, factor) :   
110        """Sets the user's overcharging coefficient."""
111        self.OverCharge = factor
[2706]112        self.isDirty = True
[2054]113       
[2937]114    def setEmail(self, email) :   
115        """Sets the user's email address."""
116        self.Email = email
117        self.isDirty = True
118       
[1041]119    def delete(self) :   
[2717]120        """Deletes an user from the database."""
[2801]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           
[1041]129       
[3060]130    def refund(self, amount, reason) :
[3056]131        """Refunds a number of credits to an user."""
132        self.consumeAccountBalance(-amount)
[3060]133        self.parent.writeNewPayment(self, -amount, reason)
[3056]134       
135       
[1041]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."""
[1062]147        try :
148            limitby = limitby.lower()
149        except AttributeError :   
150            limitby = "quota"
[2452]151        if limitby in ["quota", "balance", "noquota"] :
[1041]152            self.LimitBy = limitby
[2706]153            self.isDirty = True
[1041]154       
[2706]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       
[1041]163    def delete(self) :   
[2717]164        """Deletes a group from the database."""
[2801]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           
[1041]173       
[3056]174       
[1041]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
[2455]182        self.MaxJobSize = None
[2459]183        self.PassThrough = None
[1041]184       
[1358]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)
[3033]189            self.parent.tool.logdebug("Lazy retrieval of last job for printer %s" % self.Name)
[1358]190            return self.LastJob
[3033]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
[1358]195        else :
196            raise AttributeError, name
197           
[2455]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) :
[1041]199        """Adds a job to the printer's history."""
[2455]200        self.parent.writeJobNew(self, user, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, clienthost, jobsizebytes, jobmd5sum, jobpages, jobbilling, precomputedsize, precomputedprice)
[1041]201        # TODO : update LastJob object ? Probably not needed.
202       
[1258]203    def addPrinterToGroup(self, printer) :   
204        """Adds a printer to a printer group."""
[1259]205        if (printer not in self.parent.getParentPrinters(self)) and (printer.ident != self.ident) :
[1258]206            self.parent.writePrinterToGroup(self, printer)
[1269]207            # TODO : reset cached value for printer parents, or add new parent to cached value
[1332]208           
209    def delPrinterFromGroup(self, printer) :   
210        """Deletes a printer from a printer group."""
[1333]211        self.parent.removePrinterFromGroup(self, printer)
212        # TODO : reset cached value for printer parents, or add new parent to cached value
[1258]213       
[1041]214    def setPrices(self, priceperpage = None, priceperjob = None) :   
215        """Sets the printer's prices."""
216        if priceperpage is None :
[1711]217            priceperpage = self.PricePerPage or 0.0
[1041]218        else :   
219            self.PricePerPage = float(priceperpage)
220        if priceperjob is None :   
[1711]221            priceperjob = self.PricePerJob or 0.0
[1041]222        else :   
223            self.PricePerJob = float(priceperjob)
[2686]224        self.isDirty = True   
[1041]225       
[2686]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       
[1330]236    def delete(self) :   
[2717]237        """Deletes a printer from the database."""
[2801]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           
[1330]249       
[3056]250       
[1041]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
[2054]262        self.WarnCount = None
[2455]263        self.MaxJobSize = None
[1041]264       
[1358]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       
[1041]273    def setDateLimit(self, datelimit) :   
274        """Sets the date limit for this quota."""
[3050]275        datelimit = DateTime.ISO.ParseDateTime(str(datelimit)[:19])
[1041]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
[1367]284        self.DateLimit = None
[2054]285        self.WarnCount = 0
[2735]286        self.isDirty = True
[1041]287       
[1967]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("-") :
[2030]292            self.PageCounter += vused
293            self.LifePageCounter += vused
[1967]294        else :
[2030]295            self.PageCounter = self.LifePageCounter = vused
296        self.DateLimit = None
[2054]297        self.WarnCount = 0
[2735]298        self.isDirty = 1
[1967]299
[2066]300    def incDenyBannerCounter(self) :
301        """Increment the deny banner counter for this user quota."""
[2054]302        self.parent.increaseUserPQuotaWarnCount(self)
303        self.WarnCount = (self.WarnCount or 0) + 1
304       
[2066]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       
[1041]310    def reset(self) :   
311        """Resets page counter to 0."""
312        self.PageCounter = 0
[2030]313        self.DateLimit = None
[2735]314        self.isDirty = True
[1041]315       
[1755]316    def hardreset(self) :   
317        """Resets actual and life time page counters to 0."""
318        self.PageCounter = self.LifePageCounter = 0
[2030]319        self.DateLimit = None
[2735]320        self.isDirty = True
[1755]321       
[3036]322    def computeJobPrice(self, jobsize, inkusage=[]) :   
[1285]323        """Computes the job price as the sum of all parent printers' prices + current printer's ones."""
324        totalprice = 0.0   
325        if jobsize :
[2054]326            if self.User.OverCharge != 0.0 :    # optimization, but TODO : beware of rounding errors
327                for upq in [ self ] + self.ParentPrintersUserPQuota :
[3036]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 :   
[3245]340                                coefficients = upq.Printer.Coefficients
[3036]341                                for (ink, value) in usage.items() :
342                                    coefvalue = coefficients.get(ink, 1.0)
343                                    coefprice = (coefvalue * pageprice) / 100.0
344                                    inkprice = coefprice * value
[3245]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))
[3101]346                                    totalprice += inkprice
[2054]347        if self.User.OverCharge != 1.0 : # TODO : beware of rounding errors
348            overcharged = totalprice * self.User.OverCharge       
[3036]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))
[2054]350            return overcharged
351        else :   
352            return totalprice
[1285]353           
[3036]354    def increasePagesUsage(self, jobsize, inkusage=[]) :
[1041]355        """Increase the value of used pages and money."""
[3036]356        jobprice = self.computeJobPrice(jobsize, inkusage)
[1285]357        if jobsize :
[2409]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
[1285]364        return jobprice
[1041]365       
[2717]366    def delete(self) :   
367        """Deletes an user print quota entry from the database."""
[2801]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
[2717]373       
[3056]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       
[1041]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
[2455]392        self.MaxJobSize = None
[1041]393       
[1365]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       
[2030]402    def reset(self) :   
403        """Resets page counter to 0."""
[2801]404        for user in self.parent.getGroupMembers(self.Group) :
405            uq = self.parent.getUserPQuota(user, self.Printer)
406            uq.reset()
407            uq.save()
[2030]408        self.PageCounter = 0
409        self.DateLimit = None
[2735]410        self.isDirty = True
[2030]411       
412    def hardreset(self) :   
413        """Resets actual and life time page counters to 0."""
[2801]414        for user in self.parent.getGroupMembers(self.Group) :
415            uq = self.parent.getUserPQuota(user, self.Printer)
416            uq.hardreset()
417            uq.save()
[2030]418        self.PageCounter = self.LifePageCounter = 0
419        self.DateLimit = None
[2735]420        self.isDirty = True
[2030]421       
[1041]422    def setDateLimit(self, datelimit) :   
423        """Sets the date limit for this quota."""
[3050]424        datelimit = DateTime.ISO.ParseDateTime(str(datelimit)[:19])
[2450]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)
[1041]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
[1367]438        self.DateLimit = None
[2735]439        self.isDirty = True
[1041]440       
[2717]441    def delete(self) :   
442        """Deletes a group print quota entry from the database."""
[2801]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
[2717]448       
[3056]449       
[1274]450class StorageJob(StorageObject) :
[1692]451    """Printer's Job class."""
[1274]452    def __init__(self, parent) :
[1041]453        StorageObject.__init__(self, parent)
[1358]454        self.UserName = None
455        self.PrinterName = None
[1041]456        self.JobId = None
457        self.PrinterPageCounter = None
[1520]458        self.JobSizeBytes = None
[1041]459        self.JobSize = None
460        self.JobAction = None
461        self.JobDate = None
[1203]462        self.JobPrice = None
463        self.JobFileName = None
464        self.JobTitle = None
465        self.JobCopies = None
466        self.JobOptions = None
[1502]467        self.JobHostName = None
[2054]468        self.JobMD5Sum = None
469        self.JobPages = None
470        self.JobBillingCode = None
[2455]471        self.PrecomputedJobSize = None
472        self.PrecomputedJobPrice = None
[1041]473       
[1358]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
[3056]484           
[3060]485    def refund(self, reason) :       
[3056]486        """Refund a particular print job."""
[3057]487        if (not self.JobSize) or (self.JobAction in ("DENY", "CANCEL", "REFUND")) :
[3056]488            return
[3062]489           
490        basereason = _("Refunded %i pages and %.3f credits by %s (%s) on %s") \
491                        % (self.JobSize,
492                           self.JobPrice,
[3064]493                           self.parent.tool.originalUserName,
[3062]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)   
[3056]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 :
[3060]508                self.User.refund(self.JobPrice, reason)
[3056]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()
[3062]516            self.parent.tool.logdebug("Error while refunding job %s." % self.ident)
[3056]517            raise
518        else :   
519            self.parent.commitTransaction()
[3062]520            self.parent.tool.logdebug("Job %s refunded." % self.ident)
[1358]521       
[3056]522       
[1274]523class StorageLastJob(StorageJob) :
524    """Printer's Last Job class."""
525    def __init__(self, parent, printer) :
526        StorageJob.__init__(self, parent)
[1359]527        self.PrinterName = printer.Name # not needed
[1274]528        self.Printer = printer
529       
[3056]530       
[2340]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)
[2450]542        self.parent.flushEntry("BILLINGCODES", self.BillingCode)
[2686]543        self.isDirty = False
[2676]544        self.Exists = False
[2340]545       
[2342]546    def reset(self, balance=0.0, pagecounter=0) :   
[2340]547        """Resets the pagecounter and balance for this billing code."""
[2686]548        self.Balance = balance
549        self.PageCounter = pagecounter
550        self.isDirty = True
[2340]551       
552    def consume(self, pages, price) :
553        """Consumes some pages and credits for this billing code."""
554        if pages :
[3056]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)
[2340]562       
[3056]563       
[1130]564class BaseStorage :
565    def __init__(self, pykotatool) :
[1523]566        """Opens the storage connection."""
[1130]567        self.closed = 1
568        self.tool = pykotatool
569        self.usecache = pykotatool.config.getCaching()
[1148]570        self.disablehistory = pykotatool.config.getDisableHistory()
[1875]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.")
[1130]574        if self.usecache :
575            self.tool.logdebug("Caching enabled.")
[2450]576            self.caches = { "USERS" : {}, \
577                            "GROUPS" : {}, \
578                            "PRINTERS" : {}, \
579                            "USERPQUOTAS" : {}, \
580                            "GROUPPQUOTAS" : {}, \
581                            "JOBS" : {}, \
582                            "LASTJOBS" : {}, \
583                            "BILLINGCODES" : {} }
[1130]584       
[1240]585    def close(self) :   
586        """Must be overriden in children classes."""
587        raise RuntimeError, "BaseStorage.close() must be overriden !"
588       
[1130]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))
[1743]601            return entry   
[1130]602           
603    def cacheEntry(self, cachetype, key, value) :       
604        """Puts an entry in the cache."""
[1151]605        if self.usecache and getattr(value, "Exists", 0) :
[1130]606            self.caches[cachetype][key] = value
[1132]607            self.tool.logdebug("Cache store (%s->%s)" % (cachetype, key))
[1130]608           
[2450]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           
[1130]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       
[2342]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       
[1249]677    def getParentPrinters(self, printer) :   
678        """Extracts parent printers information for a given printer from cache."""
[1252]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)
[1364]688        for parent in printer.Parents[:] :   
689            printer.Parents.extend(self.getParentPrinters(parent))
[1368]690        uniquedic = {}   
691        for parent in printer.Parents :
692            uniquedic[parent.Name] = parent
693        printer.Parents = uniquedic.values()   
[1252]694        return printer.Parents
[1249]695       
[1137]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       
[1240]722    def getParentPrintersUserPQuota(self, userpquota) :     
[1364]723        """Returns all user print quota on the printer and all its parents recursively."""
[1365]724        upquotas = [ ]
[1240]725        for printer in self.getParentPrinters(userpquota.Printer) :
[1395]726            upq = self.getUserPQuota(userpquota.User, printer)
727            if upq.Exists :
728                upquotas.append(upq)
[1240]729        return upquotas       
730       
[1365]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) :
[1395]735            gpq = self.getGroupPQuota(grouppquota.Group, printer)
736            if gpq.Exists :
737                gpquotas.append(gpq)
[1365]738        return gpquotas       
739       
[1790]740    def databaseToUserCharset(self, text) :
741        """Converts from database format (UTF-8) to user's charset."""
[2804]742        return self.tool.UTF8ToUserCharset(text)
[1790]743       
744    def userCharsetToDatabase(self, text) :
745        """Converts from user's charset to database format (UTF-8)."""
[2804]746        return self.tool.userCharsetToUTF8(text)
[1790]747       
[2266]748    def cleanDates(self, startdate, enddate) :   
749        """Clean the dates to create a correct filter."""
[2665]750        if startdate :   
751            startdate = startdate.strip().lower()
752        if enddate :   
753            enddate = enddate.strip().lower()
754        if (not startdate) and (not enddate) :   
[2266]755            return (None, None)
[2268]756           
757        now = DateTime.now()   
758        nameddates = ('yesterday', 'today', 'now', 'tomorrow')
[2665]759        datedict = { "start" : startdate, "end" : enddate }   
[2276]760        for limit in datedict.keys() :
761            dateval = datedict[limit]
[2665]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 :   
[3050]825                        DateTime.ISO.ParseDateTime(dateval[:19])
[2276]826                    except :   
[2665]827                        dateval = None
828                datedict[limit] = dateval   
[2276]829        (start, end) = (datedict["start"], datedict["end"])
[2665]830        if start and end and (start > end) :
[2276]831            (start, end) = (end, start)
[2953]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           
[2276]844        return (start, end)   
[2266]845       
[1021]846def openConnection(pykotatool) :
[2717]847    """Returns a connection handle to the appropriate database."""
[1021]848    backendinfo = pykotatool.config.getStorageBackend()
[800]849    backend = backendinfo["storagebackend"]
[695]850    try :
[2945]851        storagebackend = imp.load_source("storagebackend", 
852                                         os.path.join(os.path.dirname(__file__),
853                                                      "storages",
854                                                      "%s.py" % backend.lower()))
[695]855    except ImportError :
[773]856        raise PyKotaStorageError, _("Unsupported quota storage backend %s") % backend
[695]857    else :   
[800]858        host = backendinfo["storageserver"]
859        database = backendinfo["storagename"]
[1087]860        admin = backendinfo["storageadmin"] or backendinfo["storageuser"]
861        adminpw = backendinfo["storageadminpw"] or backendinfo["storageuserpw"]
[1240]862        return storagebackend.Storage(pykotatool, host, database, admin, adminpw)
Note: See TracBrowser for help on using the browser.