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

Revision 3549, 34.5 kB (checked in by jerome, 14 years ago)

Removed support for the MaxJobSize? attribute for users group print quota
entries : I couldn't see a real use for this at the moment, and it would
complexify the code. This support might reappear later however. Added full
support for the MaxJobSize? attribute for user print quota entries,
editable with edpykota's new --maxjobsize command line switch. Changed
the internal handling of the MaxJobSize? attribute for printers :
internally 0 used to mean unlimited, it now allows one to forbid
printing onto a particular printer. The database upgrade script (only
for PostgreSQL) takes care of this.
IMPORTANT : the database schema changes. A database upgrade script is
provided for PostgreSQL only. The LDAP schema doesn't change to not
break any existing LDAP directory, so the pykotaMaxJobSize attribute is
still allowed on group print quota entries, but never used.
Seems to work as expected, for a change :-)
Fixes #15.

  • 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-2009 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 = 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 setMaxJobSize(self, maxjobsize) :
357        """Sets the maximal job size for this print quota entry."""
358        self.MaxJobSize = maxjobsize
359        self.isDirty = True
360
361    def delete(self) :
362        """Deletes an user print quota entry from the database."""
363        self.parent.deleteUserPQuota(self)
364        if self.parent.usecache :
365            self.parent.flushEntry("USERPQUOTAS", "%s@%s" % (self.User.Name, self.Printer.Name))
366        self.Exists = False
367        self.isDirty = False
368
369    def refund(self, nbpages) :
370        """Refunds a number of pages to an user on a particular printer."""
371        self.parent.increaseUserPQuotaPagesCounters(self, -nbpages)
372        self.PageCounter = int(self.PageCounter or 0) - nbpages
373        self.LifePageCounter = int(self.LifePageCounter or 0) - nbpages
374
375
376class StorageGroupPQuota(StorageObject) :
377    """Group Print Quota class."""
378    def __init__(self, parent, group, printer) :
379        StorageObject.__init__(self, parent)
380        self.Group = group
381        self.Printer = printer
382        self.PageCounter = None
383        self.LifePageCounter = None
384        self.SoftLimit = None
385        self.HardLimit = None
386        self.DateLimit = None
387
388    def __getattr__(self, name) :
389        """Delays data retrieval until it's really needed."""
390        if name == "ParentPrintersGroupPQuota" :
391            self.ParentPrintersGroupPQuota = (self.Group.Exists and self.Printer.Exists and self.parent.getParentPrintersGroupPQuota(self)) or []
392            return self.ParentPrintersGroupPQuota
393        else :
394            raise AttributeError, name
395
396    def reset(self) :
397        """Resets page counter to 0."""
398        for user in self.parent.getGroupMembers(self.Group) :
399            uq = self.parent.getUserPQuota(user, self.Printer)
400            uq.reset()
401            uq.save()
402        self.PageCounter = 0
403        self.DateLimit = None
404        self.isDirty = True
405
406    def hardreset(self) :
407        """Resets actual and life time page counters to 0."""
408        for user in self.parent.getGroupMembers(self.Group) :
409            uq = self.parent.getUserPQuota(user, self.Printer)
410            uq.hardreset()
411            uq.save()
412        self.PageCounter = self.LifePageCounter = 0
413        self.DateLimit = None
414        self.isDirty = True
415
416    def setDateLimit(self, datelimit) :
417        """Sets the date limit for this quota."""
418        datelimit = DateTime.ISO.ParseDateTime(str(datelimit)[:19])
419        date = "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, \
420                                                  datelimit.month, \
421                                                  datelimit.day, \
422                                                  datelimit.hour, \
423                                                  datelimit.minute, \
424                                                  datelimit.second)
425        self.parent.writeGroupPQuotaDateLimit(self, date)
426        self.DateLimit = date
427
428    def setLimits(self, softlimit, hardlimit) :
429        """Sets the soft and hard limit for this quota."""
430        self.SoftLimit = softlimit
431        self.HardLimit = hardlimit
432        self.DateLimit = None
433        self.isDirty = True
434
435    def delete(self) :
436        """Deletes a group print quota entry from the database."""
437        self.parent.deleteGroupPQuota(self)
438        if self.parent.usecache :
439            self.parent.flushEntry("GROUPPQUOTAS", "%s@%s" % (self.Group.Name, self.Printer.Name))
440        self.Exists = False
441        self.isDirty = False
442
443
444class StorageJob(StorageObject) :
445    """Printer's Job class."""
446    def __init__(self, parent) :
447        StorageObject.__init__(self, parent)
448        self.UserName = None
449        self.PrinterName = None
450        self.JobId = None
451        self.PrinterPageCounter = None
452        self.JobSizeBytes = None
453        self.JobSize = None
454        self.JobAction = None
455        self.JobDate = None
456        self.JobPrice = None
457        self.JobFileName = None
458        self.JobTitle = None
459        self.JobCopies = None
460        self.JobOptions = None
461        self.JobHostName = None
462        self.JobMD5Sum = None
463        self.JobPages = None
464        self.JobBillingCode = None
465        self.PrecomputedJobSize = None
466        self.PrecomputedJobPrice = None
467
468    def __getattr__(self, name) :
469        """Delays data retrieval until it's really needed."""
470        if name == "User" :
471            self.User = self.parent.getUser(self.UserName)
472            return self.User
473        elif name == "Printer" :
474            self.Printer = self.parent.getPrinter(self.PrinterName)
475            return self.Printer
476        else :
477            raise AttributeError, name
478
479    def refund(self, reason) :
480        """Refund a particular print job."""
481        self.parent.tool.logdebug("Refunding job %s..." % self.ident)
482        self.parent.beginTransaction()
483        try :
484            if self.JobBillingCode :
485                bcode = self.parent.getBillingCode(self.JobBillingCode)
486                bcode.refund(self.JobSize, self.JobPrice)
487
488            if self.User.Exists :
489                self.User.refund(self.JobPrice, reason)
490                if self.Printer.Exists :
491                    upq = self.parent.getUserPQuota(self.User, self.Printer)
492                    if upq.Exists :
493                        upq.refund(self.JobSize)
494            self.parent.refundJob(self.ident)
495        except :
496            self.parent.rollbackTransaction()
497            self.parent.tool.logdebug("Error while refunding job %s." % self.ident)
498            raise
499        else :
500            self.parent.commitTransaction()
501            self.parent.tool.logdebug("Job %s refunded." % self.ident)
502
503
504class StorageLastJob(StorageJob) :
505    """Printer's Last Job class."""
506    def __init__(self, parent, printer) :
507        StorageJob.__init__(self, parent)
508        self.PrinterName = printer.Name # not needed
509        self.Printer = printer
510
511
512class StorageBillingCode(StorageObject) :
513    """Billing code class."""
514    def __init__(self, parent, name) :
515        StorageObject.__init__(self, parent)
516        self.BillingCode = name
517        self.PageCounter = None
518        self.Balance = None
519
520    def delete(self) :
521        """Deletes the billing code from the database."""
522        self.parent.deleteBillingCode(self)
523        self.parent.flushEntry("BILLINGCODES", self.BillingCode)
524        self.isDirty = False
525        self.Exists = False
526
527    def reset(self, balance=0.0, pagecounter=0) :
528        """Resets the pagecounter and balance for this billing code."""
529        self.Balance = balance
530        self.PageCounter = pagecounter
531        self.isDirty = True
532
533    def consume(self, pages, price) :
534        """Consumes some pages and credits for this billing code."""
535        if pages :
536            self.parent.consumeBillingCode(self, pages, price)
537            self.PageCounter += pages
538            self.Balance -= price
539
540    def refund(self, pages, price) :
541        """Refunds a particular billing code."""
542        self.consume(-pages, -price)
543
544
545class BaseStorage :
546    def __init__(self, pykotatool) :
547        """Opens the storage connection."""
548        self.closed = True
549        self.tool = pykotatool
550        self.usecache = pykotatool.config.getCaching()
551        self.disablehistory = pykotatool.config.getDisableHistory()
552        self.privacy = pykotatool.config.getPrivacy()
553        if self.privacy :
554            pykotatool.logdebug("Jobs' title, filename and options will be hidden because of privacy concerns.")
555        if self.usecache :
556            self.tool.logdebug("Caching enabled.")
557            self.caches = { "USERS" : {}, \
558                            "GROUPS" : {}, \
559                            "PRINTERS" : {}, \
560                            "USERPQUOTAS" : {}, \
561                            "GROUPPQUOTAS" : {}, \
562                            "JOBS" : {}, \
563                            "LASTJOBS" : {}, \
564                            "BILLINGCODES" : {} }
565
566    def close(self) :
567        """Must be overriden in children classes."""
568        raise RuntimeError, "BaseStorage.close() must be overriden !"
569
570    def __del__(self) :
571        """Ensures that the database connection is closed."""
572        self.close()
573
574    def querydebug(self, qmsg) :
575        """Logs a database query, where all queries are already UTF-8 encoded."""
576        self.tool.logdebug(qmsg.decode("UTF-8", "replace"))
577
578    def hasWildCards(self, pattern) :
579        """Returns True if the pattern contains wildcards, else False."""
580        specialchars = "*?[!" # no need to check for ] since [ would be there first
581        for specialchar in specialchars :
582            if specialchar in pattern :
583                return True
584        return False
585
586    def getFromCache(self, cachetype, key) :
587        """Tries to extract something from the cache."""
588        if self.usecache :
589            entry = self.caches[cachetype].get(key)
590            if entry is not None :
591                self.tool.logdebug("Cache hit (%s->%s)" % (cachetype, key))
592            else :
593                self.tool.logdebug("Cache miss (%s->%s)" % (cachetype, key))
594            return entry
595
596    def cacheEntry(self, cachetype, key, value) :
597        """Puts an entry in the cache."""
598        if self.usecache and getattr(value, "Exists", 0) :
599            self.caches[cachetype][key] = value
600            self.tool.logdebug("Cache store (%s->%s)" % (cachetype, key))
601
602    def flushEntry(self, cachetype, key) :
603        """Removes an entry from the cache."""
604        if self.usecache :
605            try :
606                del self.caches[cachetype][key]
607            except KeyError :
608                pass
609            else :
610                self.tool.logdebug("Cache flush (%s->%s)" % (cachetype, key))
611
612    def getUser(self, username) :
613        """Returns the user from cache."""
614        user = self.getFromCache("USERS", username)
615        if user is None :
616            user = self.getUserFromBackend(username)
617            self.cacheEntry("USERS", username, user)
618        return user
619
620    def getGroup(self, groupname) :
621        """Returns the group from cache."""
622        group = self.getFromCache("GROUPS", groupname)
623        if group is None :
624            group = self.getGroupFromBackend(groupname)
625            self.cacheEntry("GROUPS", groupname, group)
626        return group
627
628    def getPrinter(self, printername) :
629        """Returns the printer from cache."""
630        printer = self.getFromCache("PRINTERS", printername)
631        if printer is None :
632            printer = self.getPrinterFromBackend(printername)
633            self.cacheEntry("PRINTERS", printername, printer)
634        return printer
635
636    def getUserPQuota(self, user, printer) :
637        """Returns the user quota information from cache."""
638        useratprinter = "%s@%s" % (user.Name, printer.Name)
639        upquota = self.getFromCache("USERPQUOTAS", useratprinter)
640        if upquota is None :
641            upquota = self.getUserPQuotaFromBackend(user, printer)
642            self.cacheEntry("USERPQUOTAS", useratprinter, upquota)
643        return upquota
644
645    def getGroupPQuota(self, group, printer) :
646        """Returns the group quota information from cache."""
647        groupatprinter = "%s@%s" % (group.Name, printer.Name)
648        gpquota = self.getFromCache("GROUPPQUOTAS", groupatprinter)
649        if gpquota is None :
650            gpquota = self.getGroupPQuotaFromBackend(group, printer)
651            self.cacheEntry("GROUPPQUOTAS", groupatprinter, gpquota)
652        return gpquota
653
654    def getPrinterLastJob(self, printer) :
655        """Extracts last job information for a given printer from cache."""
656        lastjob = self.getFromCache("LASTJOBS", printer.Name)
657        if lastjob is None :
658            lastjob = self.getPrinterLastJobFromBackend(printer)
659            self.cacheEntry("LASTJOBS", printer.Name, lastjob)
660        return lastjob
661
662    def getBillingCode(self, label) :
663        """Returns the user from cache."""
664        code = self.getFromCache("BILLINGCODES", label)
665        if code is None :
666            code = self.getBillingCodeFromBackend(label)
667            self.cacheEntry("BILLINGCODES", label, code)
668        return code
669
670    def getParentPrinters(self, printer) :
671        """Extracts parent printers information for a given printer from cache."""
672        if self.usecache :
673            if not hasattr(printer, "Parents") :
674                self.tool.logdebug("Cache miss (%s->Parents)" % printer.Name)
675                printer.Parents = self.getParentPrintersFromBackend(printer)
676                self.tool.logdebug("Cache store (%s->Parents)" % printer.Name)
677            else :
678                self.tool.logdebug("Cache hit (%s->Parents)" % printer.Name)
679        else :
680            printer.Parents = self.getParentPrintersFromBackend(printer)
681        for parent in printer.Parents[:] :
682            printer.Parents.extend(self.getParentPrinters(parent))
683        uniquedic = {}
684        for parent in printer.Parents :
685            uniquedic[parent.Name] = parent
686        printer.Parents = uniquedic.values()
687        return printer.Parents
688
689    def getGroupMembers(self, group) :
690        """Returns the group's members list from in-group cache."""
691        if self.usecache :
692            if not hasattr(group, "Members") :
693                self.tool.logdebug("Cache miss (%s->Members)" % group.Name)
694                group.Members = self.getGroupMembersFromBackend(group)
695                self.tool.logdebug("Cache store (%s->Members)" % group.Name)
696            else :
697                self.tool.logdebug("Cache hit (%s->Members)" % group.Name)
698        else :
699            group.Members = self.getGroupMembersFromBackend(group)
700        return group.Members
701
702    def getUserGroups(self, user) :
703        """Returns the user's groups list from in-user cache."""
704        if self.usecache :
705            if not hasattr(user, "Groups") :
706                self.tool.logdebug("Cache miss (%s->Groups)" % user.Name)
707                user.Groups = self.getUserGroupsFromBackend(user)
708                self.tool.logdebug("Cache store (%s->Groups)" % user.Name)
709            else :
710                self.tool.logdebug("Cache hit (%s->Groups)" % user.Name)
711        else :
712            user.Groups = self.getUserGroupsFromBackend(user)
713        return user.Groups
714
715    def getParentPrintersUserPQuota(self, userpquota) :
716        """Returns all user print quota on the printer and all its parents recursively."""
717        upquotas = [ ]
718        for printer in self.getParentPrinters(userpquota.Printer) :
719            upq = self.getUserPQuota(userpquota.User, printer)
720            if upq.Exists :
721                upquotas.append(upq)
722        return upquotas
723
724    def getParentPrintersGroupPQuota(self, grouppquota) :
725        """Returns all group print quota on the printer and all its parents recursively."""
726        gpquotas = [ ]
727        for printer in self.getParentPrinters(grouppquota.Printer) :
728            gpq = self.getGroupPQuota(grouppquota.Group, printer)
729            if gpq.Exists :
730                gpquotas.append(gpq)
731        return gpquotas
732
733    def cleanDates(self, startdate, enddate) :
734        """Clean the dates to create a correct filter."""
735        if startdate :
736            startdate = startdate.strip().lower()
737        if enddate :
738            enddate = enddate.strip().lower()
739        if (not startdate) and (not enddate) :
740            return (None, None)
741
742        now = DateTime.now()
743        nameddates = ('yesterday', 'today', 'now', 'tomorrow')
744        datedict = { "start" : startdate, "end" : enddate }
745        for limit in datedict.keys() :
746            dateval = datedict[limit]
747            if dateval :
748                for name in nameddates :
749                    if dateval.startswith(name) :
750                        try :
751                            offset = int(dateval[len(name):])
752                        except :
753                            offset = 0
754                        dateval = dateval[:len(name)]
755                        if limit == "start" :
756                            if dateval == "yesterday" :
757                                dateval = (now - 1 + offset).Format("%Y%m%d000000")
758                            elif dateval == "today" :
759                                dateval = (now + offset).Format("%Y%m%d000000")
760                            elif dateval == "now" :
761                                dateval = (now + offset).Format("%Y%m%d%H%M%S")
762                            else : # tomorrow
763                                dateval = (now + 1 + offset).Format("%Y%m%d000000")
764                        else :
765                            if dateval == "yesterday" :
766                                dateval = (now - 1 + offset).Format("%Y%m%d235959")
767                            elif dateval == "today" :
768                                dateval = (now + offset).Format("%Y%m%d235959")
769                            elif dateval == "now" :
770                                dateval = (now + offset).Format("%Y%m%d%H%M%S")
771                            else : # tomorrow
772                                dateval = (now + 1 + offset).Format("%Y%m%d235959")
773                        break
774
775                if not dateval.isdigit() :
776                    dateval = None
777                else :
778                    lgdateval = len(dateval)
779                    if lgdateval == 4 :
780                        if limit == "start" :
781                            dateval = "%s0101 00:00:00" % dateval
782                        else :
783                            dateval = "%s1231 23:59:59" % dateval
784                    elif lgdateval == 6 :
785                        if limit == "start" :
786                            dateval = "%s01 00:00:00" % dateval
787                        else :
788                            mxdate = DateTime.ISO.ParseDateTime("%s01 00:00:00" % dateval)
789                            dateval = "%s%02i 23:59:59" % (dateval, mxdate.days_in_month)
790                    elif lgdateval == 8 :
791                        if limit == "start" :
792                            dateval = "%s 00:00:00" % dateval
793                        else :
794                            dateval = "%s 23:59:59" % dateval
795                    elif lgdateval == 10 :
796                        if limit == "start" :
797                            dateval = "%s %s:00:00" % (dateval[:8], dateval[8:])
798                        else :
799                            dateval = "%s %s:59:59" % (dateval[:8], dateval[8:])
800                    elif lgdateval == 12 :
801                        if limit == "start" :
802                            dateval = "%s %s:%s:00" % (dateval[:8], dateval[8:10], dateval[10:])
803                        else :
804                            dateval = "%s %s:%s:59" % (dateval[:8], dateval[8:10], dateval[10:])
805                    elif lgdateval == 14 :
806                        dateval = "%s %s:%s:%s" % (dateval[:8], dateval[8:10], dateval[10:12], dateval[12:])
807                    else :
808                        dateval = None
809                    try :
810                        DateTime.ISO.ParseDateTime(dateval[:19])
811                    except :
812                        dateval = None
813                datedict[limit] = dateval
814        (start, end) = (datedict["start"], datedict["end"])
815        if start and end and (start > end) :
816            (start, end) = (end, start)
817        try :
818            if len(start) == 17 :
819                start = "%s-%s-%s %s" % (start[0:4], start[4:6], start[6:8], start[9:])
820        except TypeError :
821            pass
822
823        try :
824            if len(end) == 17 :
825                end = "%s-%s-%s %s" % (end[0:4], end[4:6], end[6:8], end[9:])
826        except TypeError :
827            pass
828
829        return (start, end)
830
831def openConnection(pykotatool) :
832    """Returns a connection handle to the appropriate database."""
833    backendinfo = pykotatool.config.getStorageBackend()
834    backend = backendinfo["storagebackend"]
835    try :
836        storagebackend = imp.load_source("storagebackend",
837                                         os.path.join(os.path.dirname(__file__),
838                                                      "storages",
839                                                      "%s.py" % backend.lower()))
840    except ImportError :
841        raise PyKotaStorageError, _("Unsupported quota storage backend %s") % backend
842    else :
843        host = backendinfo["storageserver"]
844        database = backendinfo["storagename"]
845        admin = backendinfo["storageadmin"] or backendinfo["storageuser"]
846        adminpw = backendinfo["storageadminpw"] or backendinfo["storageuserpw"]
847        return storagebackend.Storage(pykotatool, host, database, admin, adminpw)
Note: See TracBrowser for help on using the browser.