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

Revision 3294, 36.0 kB (checked in by jerome, 16 years ago)

Added modules to store utility functions and application
intialization code, which has nothing to do in classes.
Modified tool.py accordingly (far from being finished)
Use these new modules where necessary.
Now converts all command line arguments to unicode before
beginning to work. Added a proper logging method for already
encoded query strings.

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