root / pykota / trunk / pykota / storages / pgstorage.py @ 1067

Revision 1067, 21.6 kB (checked in by jalet, 21 years ago)

Bug fix due to a typo in LDAP code

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2#
3# PyKota : Print Quotas for CUPS and LPRng
4#
5# (c) 2003 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 2 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, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19#
20# $Id$
21#
22# $Log$
23# Revision 1.5  2003/07/07 08:33:19  jalet
24# Bug fix due to a typo in LDAP code
25#
26# Revision 1.4  2003/06/30 13:54:21  jalet
27# Sorts by user / group name
28#
29# Revision 1.3  2003/06/25 14:10:01  jalet
30# Hey, it may work (edpykota --reset excepted) !
31#
32# Revision 1.2  2003/06/12 21:09:57  jalet
33# wrongly placed code.
34#
35# Revision 1.1  2003/06/10 16:37:54  jalet
36# Deletion of the second user which is not needed anymore.
37# Added a debug configuration field in /etc/pykota.conf
38# All queries can now be sent to the logger in debug mode, this will
39# greatly help improve performance when time for this will come.
40#
41#
42#
43#
44
45from pykota.storage import PyKotaStorageError
46from pykota.storage import StorageObject,StorageUser,StorageGroup,StoragePrinter,StorageLastJob,StorageUserPQuota,StorageGroupPQuota
47
48try :
49    import pg
50except ImportError :   
51    import sys
52    # TODO : to translate or not to translate ?
53    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the PygreSQL module installed correctly." % sys.version.split()[0]
54
55class Storage :
56    def __init__(self, pykotatool, host, dbname, user, passwd) :
57        """Opens the PostgreSQL database connection."""
58        self.tool = pykotatool
59        self.debug = pykotatool.config.getDebug()
60        self.closed = 1
61        try :
62            (host, port) = host.split(":")
63            port = int(port)
64        except ValueError :   
65            port = -1         # Use PostgreSQL's default tcp/ip port (5432).
66       
67        try :
68            self.database = pg.connect(host=host, port=port, dbname=dbname, user=user, passwd=passwd)
69        except pg.error, msg :
70            raise PyKotaStorageError, msg
71        else :   
72            self.closed = 0
73            if self.debug :
74                self.tool.logger.log_message("Database opened (host=%s, port=%s, dbname=%s, user=%s)" % (host, port, dbname, user), "debug")
75           
76    def __del__(self) :       
77        """Closes the database connection."""
78        if not self.closed :
79            self.database.close()
80            self.closed = 1
81            if self.debug :
82                self.tool.logger.log_message("Database closed.", "debug")
83       
84    def beginTransaction(self) :   
85        """Starts a transaction."""
86        self.database.query("BEGIN;")
87        if self.debug :
88            self.tool.logger.log_message("Transaction begins...", "debug")
89       
90    def commitTransaction(self) :   
91        """Commits a transaction."""
92        self.database.query("COMMIT;")
93        if self.debug :
94            self.tool.logger.log_message("Transaction committed.", "debug")
95       
96    def rollbackTransaction(self) :     
97        """Rollbacks a transaction."""
98        self.database.query("ROLLBACK;")
99        if self.debug :
100            self.tool.logger.log_message("Transaction aborted.", "debug")
101       
102    def doSearch(self, query) :
103        """Does a search query."""
104        query = query.strip()   
105        if not query.endswith(';') :   
106            query += ';'
107        try :
108            if self.debug :
109                self.tool.logger.log_message("QUERY : %s" % query, "debug")
110            result = self.database.query(query)
111        except pg.error, msg :   
112            raise PyKotaStorageError, msg
113        else :   
114            if (result is not None) and (result.ntuples() > 0) : 
115                return result.dictresult()
116           
117    def doModify(self, query) :
118        """Does a (possibly multiple) modify query."""
119        query = query.strip()   
120        if not query.endswith(';') :   
121            query += ';'
122        try :
123            if self.debug :
124                self.tool.logger.log_message("QUERY : %s" % query, "debug")
125            result = self.database.query(query)
126        except pg.error, msg :   
127            raise PyKotaStorageError, msg
128           
129    def doQuote(self, field) :
130        """Quotes a field for use as a string in SQL queries."""
131        if type(field) == type(0.0) : 
132            typ = "decimal"
133        elif type(field) == type(0) :   
134            typ = "int"
135        else :   
136            typ = "text"
137        return pg._quote(field, typ)
138       
139    def getUser(self, username) :   
140        """Extracts user information given its name."""
141        user = StorageUser(self, username)
142        result = self.doSearch("SELECT * FROM users WHERE username=%s LIMIT 1" % self.doQuote(username))
143        if result :
144            fields = result[0]
145            user.ident = fields.get("id")
146            user.LimitBy = fields.get("limitby")
147            user.AccountBalance = fields.get("balance")
148            user.LifeTimePaid = fields.get("lifetimepaid")
149            user.Email = fields.get("email")
150            user.Exists = 1
151        return user
152       
153    def getGroup(self, groupname) :   
154        """Extracts group information given its name."""
155        group = StorageGroup(self, groupname)
156        result = self.doSearch("SELECT * FROM groups WHERE groupname=%s LIMIT 1" % self.doQuote(groupname))
157        if result :
158            fields = result[0]
159            group.ident = fields.get("id")
160            group.LimitBy = fields.get("limitby")
161            result = self.doSearch("SELECT SUM(balance) AS balance, SUM(lifetimepaid) AS lifetimepaid FROM users WHERE id IN (SELECT userid FROM groupsmembers WHERE groupid=%s)" % self.doQuote(group.ident))
162            if result :
163                fields = result[0]
164                group.AccountBalance = fields.get("balance")
165                group.LifeTimePaid = fields.get("lifetimepaid")
166            group.Exists = 1
167        return group
168       
169    def getPrinter(self, printername) :       
170        """Extracts printer information given its name."""
171        printer = StoragePrinter(self, printername)
172        result = self.doSearch("SELECT * FROM printers WHERE printername=%s LIMIT 1" % self.doQuote(printername))
173        if result :
174            fields = result[0]
175            printer.ident = fields.get("id")
176            printer.PricePerJob = fields.get("priceperjob")
177            printer.PricePerPage = fields.get("priceperpage")
178            printer.LastJob = self.getPrinterLastJob(printer)
179            printer.Exists = 1
180        return printer   
181           
182    def getUserGroups(self, user) :       
183        """Returns the user's groups list."""
184        groups = []
185        result = self.doSearch("SELECT groupname FROM groupsmembers JOIN groups ON groupsmembers.groupid=groups.id WHERE userid=%s" % self.doQuote(user.ident))
186        if result :
187            for record in result :
188                groups.append(self.getGroup(record.get("groupname")))
189        return groups       
190       
191    def getGroupMembers(self, group) :       
192        """Returns the group's members list."""
193        groupmembers = []
194        result = self.doSearch("SELECT * FROM groupsmembers JOIN users ON groupsmembers.userid=users.id WHERE groupid=%s" % self.doQuote(group.ident))
195        if result :
196            for record in result :
197                user = StorageUser(self, record.get("username"))
198                user.ident = record.get("userid")
199                user.LimitBy = record.get("limitby")
200                user.AccountBalance = record.get("balance")
201                user.LifeTimePaid = record.get("lifetimepaid")
202                user.Email = record.get("email")
203                user.Exists = 1
204                groupmembers.append(user)
205        return groupmembers       
206       
207    def getUserPQuota(self, user, printer) :       
208        """Extracts a user print quota."""
209        userpquota = StorageUserPQuota(self, user, printer)
210        if user.Exists :
211            result = self.doSearch("SELECT id, lifepagecounter, pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(user.ident), self.doQuote(printer.ident)))
212            if result :
213                fields = result[0]
214                userpquota.ident = fields.get("id")
215                userpquota.PageCounter = fields.get("pagecounter")
216                userpquota.LifePageCounter = fields.get("lifepagecounter")
217                userpquota.SoftLimit = fields.get("softlimit")
218                userpquota.HardLimit = fields.get("hardlimit")
219                userpquota.DateLimit = fields.get("datelimit")
220                userpquota.Exists = 1
221        return userpquota
222       
223    def getGroupPQuota(self, group, printer) :       
224        """Extracts a group print quota."""
225        grouppquota = StorageGroupPQuota(self, group, printer)
226        if group.Exists :
227            result = self.doSearch("SELECT id, softlimit, hardlimit, datelimit FROM grouppquota WHERE groupid=%s AND printerid=%s" % (self.doQuote(group.ident), self.doQuote(printer.ident)))
228            if result :
229                fields = result[0]
230                grouppquota.ident = fields.get("id")
231                grouppquota.SoftLimit = fields.get("softlimit")
232                grouppquota.HardLimit = fields.get("hardlimit")
233                grouppquota.DateLimit = fields.get("datelimit")
234                result = self.doSearch("SELECT SUM(lifepagecounter) AS lifepagecounter, SUM(pagecounter) AS pagecounter FROM userpquota WHERE printerid=%s AND userid IN (SELECT userid FROM groupsmembers WHERE groupid=%s)" % (self.doQuote(printer.ident), self.doQuote(group.ident)))
235                if result :
236                    fields = result[0]
237                    grouppquota.PageCounter = fields.get("pagecounter")
238                    grouppquota.LifePageCounter = fields.get("lifepagecounter")
239                grouppquota.Exists = 1
240        return grouppquota
241       
242    def getPrinterLastJob(self, printer) :       
243        """Extracts a printer's last job information."""
244        lastjob = StorageLastJob(self, printer)
245        result = self.doSearch("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize, jobdate FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printer.ident))
246        if result :
247            fields = result[0]
248            lastjob.ident = fields.get("id")
249            lastjob.JobId = fields.get("jobid")
250            lastjob.User = self.getUser(fields.get("username"))
251            lastjob.PrinterPageCounter = fields.get("pagecounter")
252            lastjob.JobSize = fields.get("jobsize")
253            lastjob.JobAction = fields.get("action")
254            lastjob.JobDate = fields.get("jobdate")
255            lastjob.Exists = 1
256        return lastjob
257       
258    def getMatchingPrinters(self, printerpattern) :
259        """Returns the list of all printers for which name matches a certain pattern."""
260        printers = []
261        # We 'could' do a SELECT printername FROM printers WHERE printername LIKE ...
262        # but we don't because other storages semantics may be different, so every
263        # storage should use fnmatch to match patterns and be storage agnostic
264        result = self.doSearch("SELECT * FROM printers")
265        if result :
266            for record in result :
267                if self.tool.matchString(record["printername"], [ printerpattern ]) :
268                    printer = StoragePrinter(self, record["printername"])
269                    printer.ident = record.get("id")
270                    printer.PricePerJob = record.get("priceperjob")
271                    printer.PricePerPage = record.get("priceperpage")
272                    printer.LastJob = self.getPrinterLastJob(printer)
273                    printer.Exists = 1
274                    printers.append(printer)
275        return printers       
276       
277    def getPrinterUsersAndQuotas(self, printer, names=None) :       
278        """Returns the list of users who uses a given printer, along with their quotas."""
279        usersandquotas = []
280        result = self.doSearch("SELECT users.id as uid,username,balance,lifetimepaid,limitby,userpquota.id,lifepagecounter,pagecounter,softlimit,hardlimit,datelimit FROM users JOIN userpquota ON users.id=userpquota.userid AND printerid=%s ORDER BY username ASC" % self.doQuote(printer.ident))
281        if result :
282            for record in result :
283                user = StorageUser(self, record.get("username"))
284                if (names is None) or self.tool.matchString(user.Name, names) :
285                    user.ident = record.get("uid")
286                    user.LimitBy = record.get("limitby")
287                    user.AccountBalance = record.get("balance")
288                    user.LifeTimePaid = record.get("lifetimepaid")
289                    user.Email = record.get("email")    # TODO : Always None here
290                    user.Exists = 1
291                    userpquota = StorageUserPQuota(self, user, printer)
292                    userpquota.ident = record.get("id")
293                    userpquota.PageCounter = record.get("pagecounter")
294                    userpquota.LifePageCounter = record.get("lifepagecounter")
295                    userpquota.SoftLimit = record.get("softlimit")
296                    userpquota.HardLimit = record.get("hardlimit")
297                    userpquota.DateLimit = record.get("datelimit")
298                    userpquota.Exists = 1
299                    usersandquotas.append((user, userpquota))
300        return usersandquotas
301               
302    def getPrinterGroupsAndQuotas(self, printer, names=None) :       
303        """Returns the list of groups which uses a given printer, along with their quotas."""
304        groupsandquotas = []
305        result = self.doSearch("SELECT groupname FROM groups JOIN grouppquota ON groups.id=grouppquota.groupid AND printerid=%s ORDER BY groupname ASC" % self.doQuote(printer.ident))
306        if result :
307            for record in result :
308                group = self.getGroup(record.get("groupname"))
309                if (names is None) or self.tool.matchString(group.Name, names) :
310                    grouppquota = self.getGroupPQuota(group, printer)
311                    groupsandquotas.append((group, grouppquota))
312        return groupsandquotas
313       
314    def addPrinter(self, printername) :       
315        """Adds a printer to the quota storage, returns it."""
316        self.doModify("INSERT INTO printers (printername) VALUES (%s)" % self.doQuote(printername))
317        return self.getPrinter(printername)
318       
319    def addUser(self, user) :       
320        """Adds a user to the quota storage, returns its id."""
321        self.doModify("INSERT INTO users (username, limitby, balance, lifetimepaid) VALUES (%s, %s, %s, %s)" % (self.doQuote(user.Name), self.doQuote(user.LimitBy), self.doQuote(user.AccountBalance), self.doQuote(user.LifeTimePaid)))
322        return self.getUser(user.Name)
323       
324    def addGroup(self, group) :       
325        """Adds a group to the quota storage, returns its id."""
326        self.doModify("INSERT INTO groups (groupname, limitby) VALUES (%s, %s)" % (self.doQuote(group.Name), self.doQuote(group.LimitBy)))
327        return self.getGroup(group.Name)
328
329    def addUserToGroup(self, user, group) :   
330        """Adds an user to a group."""
331        result = self.doModify("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(group.ident), self.doQuote(user.ident)))
332        try :
333            mexists = self.doParseResult(result)[0]["mexists"]
334        except TypeError :   
335            mexists = 0
336        if not mexists :   
337            self.doModify("INSERT INTO groupsmembers (groupid, userid) VALUES (%s, %s)" % (self.doQuote(group.ident), self.doQuote(user.ident)))
338           
339    def addUserPQuota(self, user, printer) :
340        """Initializes a user print quota on a printer."""
341        self.doModify("INSERT INTO userpquota (userid, printerid) VALUES (%s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident)))
342        return self.getUserPQuota(user, printer)
343       
344    def addGroupPQuota(self, group, printer) :
345        """Initializes a group print quota on a printer."""
346        self.doModify("INSERT INTO grouppquota (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(group.ident), self.doQuote(printer.ident)))
347        return self.getGroupPQuota(group, printer)
348       
349    def writePrinterPrices(self, printer) :   
350        """Write the printer's prices back into the storage."""
351        self.doModify("UPDATE printers SET priceperpage=%s, priceperjob=%s WHERE printerid=%s" % (self.doQuote(printer.PricePerPage), self.doQuote(printer.PricePerJob), self.doQuote(printer.ident)))
352       
353    def writeUserLimitBy(self, user, limitby) :   
354        """Sets the user's limiting factor."""
355        self.doModify("UPDATE users SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(user.ident)))
356       
357    def writeGroupLimitBy(self, group, limitby) :   
358        """Sets the group's limiting factor."""
359        self.doModify("UPDATE groups SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(group.ident)))
360       
361    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :   
362        """Sets the date limit permanently for a user print quota."""
363        self.doModify("UPDATE userpquota SET datelimit::TIMESTAMP=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(userpquota.ident)))
364           
365    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :   
366        """Sets the date limit permanently for a group print quota."""
367        self.doModify("UPDATE grouppquota SET datelimit::TIMESTAMP=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(grouppquota.ident)))
368       
369    def writeUserPQuotaPagesCounters(self, userpquota, newpagecounter, newlifepagecounter) :   
370       """Sets the new page counters permanently for a user print quota."""
371       self.doModify("UPDATE userpquota SET pagecounter=%s,lifepagecounter=%s WHERE id=%s" % (self.doQuote(newpagecounter), self.doQuote(newlifepagecounter), self.doQuote(userpquota.ident)))
372       
373    def writeUserAccountBalance(self, user, newbalance, newlifetimepaid=None) :   
374       """Sets the new account balance and eventually new lifetime paid."""
375       if newlifetimepaid is not None :
376           self.doModify("UPDATE users SET balance=%s, lifetimepaid=%s WHERE id=%s" % (self.doQuote(newbalance), self.doQuote(newlifetimepaid), self.doQuote(user.ident)))
377       else :   
378           self.doModify("UPDATE users SET balance=%s WHERE id=%s" % (self.doQuote(newbalance), self.doQuote(user.ident)))
379           
380    def writeLastJobSize(self, lastjob, jobsize) :       
381        """Sets the last job's size permanently."""
382        self.doModify("UPDATE jobhistory SET jobsize=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(lastjob.ident)))
383       
384    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None) :   
385        """Adds a job in a printer's history."""
386        if jobsize is not None :
387            self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, jobsize) VALUES (%s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize)))
388        else :   
389            self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action) VALUES (%s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action)))
390           
391    def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
392        """Sets soft and hard limits for a user quota."""
393        self.doModify("UPDATE userpquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE id=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userpquota.ident)))
394       
395    def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
396        """Sets soft and hard limits for a group quota on a specific printer given (groupid, printerid)."""
397        self.doModify("UPDATE grouppquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE id=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(grouppquota.ident)))
398
399    def deleteUser(self, user) :   
400        """Completely deletes an user from the Quota Storage."""
401        # TODO : What should we do if we delete the last person who used a given printer ?
402        # TODO : we can't reassign the last job to the previous one, because next user would be
403        # TODO : incorrectly charged (overcharged).
404        for q in [ 
405                    "DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(user.ident),
406                    "DELETE FROM jobhistory WHERE userid=%s" % self.doQuote(user.ident),
407                    "DELETE FROM userpquota WHERE userid=%s" % self.doQuote(user.ident),
408                    "DELETE FROM users WHERE id=%s" % self.doQuote(user.ident),
409                  ] :
410            self.doModify(q)
411       
412    def deleteGroup(self, group) :   
413        """Completely deletes a group from the Quota Storage."""
414        for q in [
415                   "DELETE FROM groupsmembers WHERE groupid=%s" % self.doQuote(group.ident),
416                   "DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(group.ident),
417                   "DELETE FROM groups WHERE id=%s" % self.doQuote(group.ident),
418                 ] : 
419            self.doModify(q)
420       
Note: See TracBrowser for help on using the browser.