root / pykota / trunk / pykota / storages / sql.py @ 925

Revision 925, 16.5 kB (checked in by jalet, 21 years ago)

Printing can now be limited either by user's account balance or by
page quota (the default). Quota report doesn't include account balance
yet, though.

  • 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
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.26  2003/04/16 08:53:14  jalet
24# Printing can now be limited either by user's account balance or by
25# page quota (the default). Quota report doesn't include account balance
26# yet, though.
27#
28# Revision 1.25  2003/04/15 21:58:33  jalet
29# edpykota now accepts a --delete option.
30# Preparation to allow edpykota to accept much more command line options
31# (WARNING : docstring is OK, but code isn't !)
32#
33# Revision 1.24  2003/04/15 13:55:28  jalet
34# Options --limitby and --balance added to edpykota
35#
36# Revision 1.23  2003/04/15 11:30:57  jalet
37# More work done on money print charging.
38# Minor bugs corrected.
39# All tools now access to the storage as priviledged users, repykota excepted.
40#
41# Revision 1.22  2003/04/10 21:47:20  jalet
42# Job history added. Upgrade script neutralized for now !
43#
44# Revision 1.21  2003/04/08 20:38:08  jalet
45# The last job Id is saved now for each printer, this will probably
46# allow other accounting methods in the future.
47#
48# Revision 1.20  2003/03/29 13:45:27  jalet
49# GPL paragraphs were incorrectly (from memory) copied into the sources.
50# Two README files were added.
51# Upgrade script for PostgreSQL pre 1.01 schema was added.
52#
53# Revision 1.19  2003/02/27 08:41:49  jalet
54# DATETIME is not supported anymore in PostgreSQL 7.3 it seems, but
55# TIMESTAMP is.
56#
57# Revision 1.18  2003/02/10 12:07:31  jalet
58# Now repykota should output the recorded total page number for each printer too.
59#
60# Revision 1.17  2003/02/10 08:41:36  jalet
61# edpykota's --reset command line option resets the limit date too.
62#
63# Revision 1.16  2003/02/08 22:39:46  jalet
64# --reset command line option added
65#
66# Revision 1.15  2003/02/08 22:12:09  jalet
67# Life time counter for users and groups added.
68#
69# Revision 1.14  2003/02/07 22:13:13  jalet
70# Perhaps edpykota is now able to add printers !!! Oh, stupid me !
71#
72# Revision 1.13  2003/02/07 00:08:52  jalet
73# Typos
74#
75# Revision 1.12  2003/02/06 23:20:03  jalet
76# warnpykota doesn't need any user/group name argument, mimicing the
77# warnquota disk quota tool.
78#
79# Revision 1.11  2003/02/06 15:05:13  jalet
80# self was forgotten
81#
82# Revision 1.10  2003/02/06 15:03:11  jalet
83# added a method to set the limit date
84#
85# Revision 1.9  2003/02/06 14:52:35  jalet
86# Forgotten import
87#
88# Revision 1.8  2003/02/06 14:49:04  jalet
89# edpykota should be ok now
90#
91# Revision 1.7  2003/02/06 14:28:59  jalet
92# edpykota should be ok, minus some typos
93#
94# Revision 1.6  2003/02/06 09:19:02  jalet
95# More robust behavior (hopefully) when the user or printer is not managed
96# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
97# printer and/or user not 'yet?' in storage.
98#
99# Revision 1.5  2003/02/05 23:26:22  jalet
100# Incorrect handling of grace delay
101#
102# Revision 1.4  2003/02/05 23:02:10  jalet
103# Typo
104#
105# Revision 1.3  2003/02/05 23:00:12  jalet
106# Forgotten import
107# Bad datetime conversion
108#
109# Revision 1.2  2003/02/05 22:28:38  jalet
110# More robust storage
111#
112# Revision 1.1  2003/02/05 21:28:17  jalet
113# Initial import into CVS
114#
115#
116#
117
118import fnmatch
119
120class SQLStorage :   
121    def getMatchingPrinters(self, printerpattern) :
122        """Returns the list of all printers as tuples (id, name) for printer names which match a certain pattern."""
123        printerslist = []
124        # We 'could' do a SELECT printername FROM printers WHERE printername LIKE ...
125        # but we don't because other storages semantics may be different, so every
126        # storage should use fnmatch to match patterns and be storage agnostic
127        result = self.doQuery("SELECT id, printername FROM printers")
128        result = self.doParseResult(result)
129        if result is not None :
130            for printer in result :
131                if fnmatch.fnmatchcase(printer["printername"], printerpattern) :
132                    printerslist.append((printer["id"], printer["printername"]))
133        return printerslist       
134           
135    def getPrinterId(self, printername) :       
136        """Returns a printerid given a printername."""
137        result = self.doQuery("SELECT id FROM printers WHERE printername=%s" % self.doQuote(printername))
138        try :
139            return self.doParseResult(result)[0]["id"]
140        except TypeError :      # Not found   
141            return
142           
143    def getPrinterPrices(self, printerid) :       
144        """Returns a printer prices per page and per job given a printerid."""
145        result = self.doQuery("SELECT priceperpage, priceperjob FROM printers WHERE id=%s" % self.doQuote(printerid))
146        try :
147            printerprices = self.doParseResult(result)[0]
148            return (printerprices["priceperpage"], printerprices["priceperjob"])
149        except TypeError :      # Not found   
150            return
151           
152    def setPrinterPrices(self, printerid, perpage, perjob) :
153        """Sets prices per job and per page for a given printer."""
154        self.doQuery("UPDATE printers SET priceperpage=%s, priceperjob=%s WHERE id=%s" % (self.doQuote(perpage), self.doQuote(perjob), self.doQuote(printerid)))
155   
156    def getUserId(self, username) :
157        """Returns a userid given a username."""
158        result = self.doQuery("SELECT id FROM users WHERE username=%s" % self.doQuote(username))
159        try :
160            return self.doParseResult(result)[0]["id"]
161        except TypeError :      # Not found
162            return
163           
164    def getGroupId(self, groupname) :
165        """Returns a groupid given a grupname."""
166        result = self.doQuery("SELECT id FROM groups WHERE groupname=%s" % self.doQuote(groupname))
167        try :
168            return self.doParseResult(result)[0]["id"]
169        except TypeError :      # Not found
170            return
171           
172    def getJobHistoryId(self, jobid, userid, printerid) :       
173        """Returns the history line's id given a (jobid, userid, printerid)."""
174        result = self.doQuery("SELECT id FROM jobhistory WHERE jobid=%s AND userid=%s AND printerid=%s" % (self.doQuote(jobid), self.doQuote(userid), self.doQuote(printerid)))
175        try :
176            return self.doParseResult(result)[0]["id"]
177        except TypeError :      # Not found   
178            return
179           
180    def getPrinterUsers(self, printerid) :       
181        """Returns the list of usernames which uses a given printer."""
182        result = self.doQuery("SELECT DISTINCT id, username FROM users WHERE id IN (SELECT userid FROM userpquota WHERE printerid=%s) ORDER BY username" % self.doQuote(printerid))
183        result = self.doParseResult(result)
184        if result is None :
185            return []
186        else :   
187            return [(record["id"], record["username"]) for record in result]
188       
189    def getPrinterGroups(self, printerid) :       
190        """Returns the list of groups which uses a given printer."""
191        result = self.doQuery("SELECT DISTINCT id, groupname FROM groups WHERE id IN (SELECT groupid FROM grouppquota WHERE printerid=%s)" % self.doQuote(printerid))
192        result = self.doParseResult(result)
193        if result is None :
194            return []
195        else :   
196            return [(record["id"], record["groupname"]) for record in result]
197       
198    def addPrinter(self, printername) :       
199        """Adds a printer to the quota storage, returns its id."""
200        self.doQuery("INSERT INTO printers (printername) VALUES (%s)" % self.doQuote(printername))
201        return self.getPrinterId(printername)
202       
203    def addUser(self, username) :       
204        """Adds a user to the quota storage, returns its id."""
205        self.doQuery("INSERT INTO users (username) VALUES (%s)" % self.doQuote(username))
206        return self.getUserId(username)
207       
208    def addGroup(self, groupname) :       
209        """Adds a group to the quota storage, returns its id."""
210        self.doQuery("INSERT INTO groups (groupname) VALUES (%s)" % self.doQuote(groupname))
211        return self.getGroupId(groupname)
212       
213    def addUserPQuota(self, username, printerid) :
214        """Initializes a user print quota on a printer, adds the user to the quota storage if needed."""
215        userid = self.getUserId(username)     
216        if userid is None :   
217            userid = self.addUser(username)
218        self.doQuery("INSERT INTO userpquota (userid, printerid) VALUES (%s, %s)" % (self.doQuote(userid), self.doQuote(printerid)))
219        return (userid, printerid)
220       
221    def addGroupPQuota(self, groupname, printerid) :
222        """Initializes a group print quota on a printer, adds the group to the quota storage if needed."""
223        groupid = self.getGroupId(groupname)     
224        if groupid is None :   
225            groupid = self.addUser(groupname)
226        self.doQuery("INSERT INTO grouppquota (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(groupid), self.doQuote(printerid)))
227        return (groupid, printerid)
228       
229    def increaseUserBalance(self, userid, amount) :   
230        """Increases (or decreases) an user's account balance by a given amount."""
231        self.doQuery("UPDATE users SET balance=balance+(%s), lifetimepaid=lifetimepaid+(%s) WHERE id=%s" % (self.doQuote(amount), self.doQuote(amount), self.doQuote(userid)))
232       
233    def getUserBalance(self, userid) :   
234        """Returns the current account balance for a given user."""
235        result = self.doQuery("SELECT balance FROM users WHERE id=%s" % self.doQuote(userid))
236        try :
237            return self.doParseResult(result)[0]["balance"]
238        except TypeError :      # Not found   
239            return
240       
241    def getUserLimitBy(self, userid) :   
242        """Returns the way in which user printing is limited."""
243        result = self.doQuery("SELECT limitby FROM users WHERE id=%s" % self.doQuote(userid))
244        try :
245            return self.doParseResult(result)[0]["limitby"]
246        except TypeError :      # Not found   
247            return
248       
249    def getGroupLimitBy(self, groupid) :   
250        """Returns the way in which group printing is limited."""
251        result = self.doQuery("SELECT limitby FROM groups WHERE id=%s" % self.doQuote(groupid))
252        try :
253            return self.doParseResult(result)[0]["limitby"]
254        except TypeError :      # Not found   
255            return
256       
257    def setUserBalance(self, userid, balance) :   
258        """Sets the account balance for a given user to a fixed value."""
259        current = self.getUserBalance(userid)
260        difference = balance - current
261        self.increaseUserBalance(userid, difference)
262       
263    def limitUserBy(self, userid, limitby) :   
264        """Limits a given user based either on print quota or on account balance."""
265        self.doQuery("UPDATE users SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(userid)))
266       
267    def limitGroupBy(self, groupid, limitby) :   
268        """Limits a given group based either on print quota or on sum of its users' account balances."""
269        self.doQuery("UPDATE groups SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(groupid)))
270       
271    def setUserPQuota(self, userid, printerid, softlimit, hardlimit) :
272        """Sets soft and hard limits for a user quota on a specific printer given (userid, printerid)."""
273        self.doQuery("UPDATE userpquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE userid=%s AND printerid=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userid), self.doQuote(printerid)))
274       
275    def resetUserPQuota(self, userid, printerid) :   
276        """Resets the page counter to zero for a user on a printer. Life time page counter is kept unchanged."""
277        self.doQuery("UPDATE userpquota SET pagecounter=0, datelimit=NULL WHERE userid=%s AND printerid=%s" % (self.doQuote(userid), self.doQuote(printerid)))
278       
279    def updateUserPQuota(self, userid, printerid, pagecount) :
280        """Updates the used user Quota information given (userid, printerid) and a job size in pages."""
281        jobprice = self.computePrinterJobPrice(printerid, pagecount)
282        queries = []   
283        queries.append("UPDATE userpquota SET lifepagecounter=lifepagecounter+(%s), pagecounter=pagecounter+(%s) WHERE userid=%s AND printerid=%s" % (self.doQuote(pagecount), self.doQuote(pagecount), self.doQuote(userid), self.doQuote(printerid)))
284        queries.append("UPDATE users SET balance=balance-(%s) WHERE id=%s" % (self.doQuote(jobprice), self.doQuote(userid)))
285        self.doQuery(queries)
286       
287    def getUserPQuota(self, userid, printerid) :
288        """Returns the Print Quota information for a given (userid, printerid)."""
289        result = self.doQuery("SELECT lifepagecounter, pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(userid), self.doQuote(printerid)))
290        try :
291            return self.doParseResult(result)[0]
292        except TypeError :      # Not found   
293            return
294       
295    def setUserDateLimit(self, userid, printerid, datelimit) :
296        """Sets the limit date for a soft limit to become an hard one given (userid, printerid)."""
297        self.doQuery("UPDATE userpquota SET datelimit=%s::TIMESTAMP WHERE userid=%s AND printerid=%s" % (self.doQuote("%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second)), self.doQuote(userid), self.doQuote(printerid)))
298       
299    def addJobToHistory(self, jobid, userid, printerid, pagecounter, action) :
300        """Adds a job to the history: (jobid, userid, printerid, last page counter taken from requester)."""
301        self.doQuery("INSERT INTO jobhistory (jobid, userid, printerid, pagecounter, action) VALUES (%s, %s, %s, %s, %s)" % (self.doQuote(jobid), self.doQuote(userid), self.doQuote(printerid), self.doQuote(pagecounter), self.doQuote(action)))
302        return self.getJobHistoryId(jobid, userid, printerid) # in case jobid is not sufficient
303   
304    def updateJobSizeInHistory(self, historyid, jobsize) :
305        """Updates a job size in the history given the history line's id."""
306        self.doQuery("UPDATE jobhistory SET jobsize=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(historyid)))
307   
308    def getPrinterPageCounter(self, printerid) :
309        """Returns the last page counter value for a printer given its id, also returns last username, last jobid and history line id."""
310        result = self.doQuery("SELECT jobhistory.id, jobid, userid, username, pagecounter FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printerid))
311        try :
312            return self.doParseResult(result)[0]
313        except TypeError :      # Not found
314            return
315       
316    def deleteUser(self, userid) :   
317        """Completely deletes an user from the Quota Storage."""
318        queries = []
319        queries.append("DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(userid))
320        queries.append("DELETE FROM jobhistory WHERE userid=%s" % self.doQuote(userid))
321        queries.append("DELETE FROM userpquota WHERE userid=%s" % self.doQuote(userid))
322        queries.append("DELETE FROM users WHERE id=%s" % self.doQuote(userid))
323        # TODO : What should we do if we delete the last person who used a given printer ?
324        self.doQuery(queries)
325       
326    def deleteGroup(self, groupid) :   
327        """Completely deletes an user from the Quota Storage."""
328        queries = []
329        queries.append("DELETE FROM groupsmembers WHERE groupid=%s" % self.doQuote(groupid))
330        queries.append("DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(groupid))
331        queries.append("DELETE FROM groups WHERE id=%s" % self.doQuote(groupid))
332        self.doQuery(queries)
333       
334    def computePrinterJobPrice(self, printerid, jobsize) :   
335        """Returns the price for a job on a given printer."""
336        prices = self.getPrinterPrices(printerid)
337        if prices is None :
338            perpage = perjob = 0.0
339        else :   
340            (perpage, perjob) = prices
341        return perjob + (perpage * jobsize)
Note: See TracBrowser for help on using the browser.