64 | | class Printer : |
65 | | """A class for PyKota Printers.""" |
66 | | def __init__(self, printername, priceperpage, priceperjob): |
67 | | """Initialize printer datas.""" |
68 | | self.PrinterName = printername |
69 | | try : |
70 | | self.PricePerPage = float(priceperpage) |
71 | | except (ValueError, TypeError) : |
72 | | self.PricePerPage = 0.0 |
73 | | try : |
74 | | self.PricePerJob = float(priceperjob) |
75 | | except (ValueError, TypeError) : |
76 | | self.PricePerJob = 0.0 |
77 | | |
78 | | class UserQuota : |
79 | | """A class for PyKota User Print Quota entries.""" |
80 | | def __init__(self, printername, pagecount, softlimit, hardlimit, datelimit): |
81 | | """Initialize user print quota datas.""" |
82 | | self.PrinterName = printername |
83 | | try : |
84 | | self.PageCounter = int(pagecount) |
85 | | except (ValueError, TypeError) : |
86 | | self.PageCounter = 0 |
87 | | try : |
88 | | self.SoftLimit = int(softlimit) |
89 | | except (ValueError, TypeError) : |
90 | | self.SoftLimit = None |
91 | | try : |
92 | | self.HardLimit = int(hardlimit) |
93 | | except (ValueError, TypeError) : |
94 | | self.HardLimit = None |
95 | | self.DateLimit = datelimit |
96 | | |
97 | | class User : |
98 | | """A class for PyKota users.""" |
99 | | def __init__(self, username, userinfo, userquotas, printers) : |
100 | | """Initialize user datas.""" |
101 | | self.UserName = username |
102 | | self.LimitBy = userinfo["limitby"][0].lower() |
103 | | try : |
104 | | self.Balance = float(userinfo["balance"][0]) |
105 | | except (ValueError, TypeError) : |
106 | | self.Balance = 0.0 |
107 | | try : |
108 | | self.LifeTimePaid = float(userinfo["lifetimepaid"][0]) |
109 | | except (ValueError, TypeError) : |
110 | | self.LifeTimePaid = 0.0 |
111 | | self.Printers = {} |
112 | | self.Quotas = {} |
113 | | for i in range(len(userquotas["printername"])) : |
114 | | printername = userquotas["printername"][i] |
115 | | try : |
116 | | pindex = printers["printername"].index(printername) |
117 | | except (ValueError, KeyError) : |
118 | | pass |
119 | | else : |
120 | | self.Printers[printername] = Printer(printername, \ |
121 | | printers["priceperpage"][pindex],\ |
122 | | printers["priceperjob"][pindex]) |
123 | | pagecounter = userquotas["pagecounter"][i] |
124 | | softlimit = userquotas["softlimit"][i] |
125 | | hardlimit = userquotas["hardlimit"][i] |
126 | | datelimit = userquotas["datelimit"][i] |
127 | | self.Quotas[printername] = UserQuota(printername, pagecounter, \ |
128 | | softlimit, hardlimit, \ |
129 | | datelimit) |
130 | | |
131 | | class CGINetworkInterface : |
132 | | """A class for all network interactions.""" |
133 | | def __init__(self, cgiurl, authname=None, authpw=None) : |
134 | | """Initialize CGI connection datas.""" |
135 | | self.cgiurl = cgiurl |
136 | | self.authname = authname # TODO : at least do basic auth |
137 | | self.authpw = authpw |
138 | | |
139 | | def retrieveDatas(self, arguments, fieldnames) : |
140 | | """Retrieve datas from the CGI script.""" |
141 | | args = { "report" : 1, |
142 | | "format" : "ssv", |
143 | | } |
144 | | args.update(arguments) |
145 | | answer = {} |
146 | | try : |
147 | | url = "%s?%s" % (self.cgiurl, urllib.urlencode(args)) |
148 | | u = urllib2.urlopen(url) |
149 | | lines = u.readlines() |
150 | | except IOError, msg : |
151 | | raise IOError, _("Unable to retrieve %s : %s") % (url, msg) |
152 | | else : |
153 | | u.close() |
154 | | try : |
155 | | lines = [ line.strip().split(";") for line in lines ] |
156 | | fields = [field[1:-1] for field in lines[0]] |
157 | | indices = [fields.index(fname) for fname in fieldnames] |
158 | | answer = dict([ (fieldname, \ |
159 | | [ line[fields.index(fieldname)][1:-1] \ |
160 | | for line in lines[1:] ]) \ |
161 | | for fieldname in fieldnames ]) |
162 | | except : |
163 | | raise ValueError, _("Invalid datas retrieved from %s") % url |
164 | | return answer |
165 | | |
166 | | def getPrinters(self) : |
167 | | """Retrieve the printer's names.""" |
168 | | arguments = { "datatype" : "printers", |
169 | | } |
170 | | return self.retrieveDatas(arguments, ["printername", "priceperpage", \ |
171 | | "priceperjob"]) |
172 | | |
173 | | def getUser(self, username) : |
174 | | """Retrieve the user's information.""" |
175 | | arguments = { "datatype" : "users", |
176 | | "filter" : "username=%s" % username, |
177 | | } |
178 | | return self.retrieveDatas(arguments, ("limitby", "balance", \ |
179 | | "lifetimepaid")) |
180 | | |
181 | | def getUserPQuotas(self, username) : |
182 | | """Retrieve the user's print quota information.""" |
183 | | arguments = { "datatype" : "upquotas", |
184 | | "filter" : "username=%s" % username, |
185 | | } |
186 | | return self.retrieveDatas(arguments, ("printername", "pagecounter", \ |
187 | | "softlimit", "hardlimit", \ |
188 | | "datelimit")) |
189 | | |
190 | | def getUserInfo(self, username) : |
191 | | """Retrieves the user account and quota information.""" |
192 | | info = self.getUser(username) |
193 | | if info : |
194 | | quotas = self.getUserPQuotas(username) |
195 | | return User(username, info, \ |
196 | | self.getUserPQuotas(username), \ |
197 | | self.getPrinters()) |
198 | | |
199 | | class PyKotIconGrid(gridlib.Grid) : |
200 | | """A class for user print quota entries.""" |
201 | | def __init__(self, parent, user) : |
202 | | gridlib.Grid.__init__(self, parent, -1) |
203 | | nbrows = len(user.Quotas.keys()) |
204 | | nbcols = 4 |
205 | | if user.LimitBy == "balance" : |
206 | | nbrows += 1 |
207 | | nbcols -= 1 |
208 | | self.CreateGrid(nbrows, nbcols) |
209 | | self.EnableEditing(False) |
210 | | if user.LimitBy == "balance" : |
211 | | self.SetColLabelValue(0, _("Page Counter")) |
212 | | self.SetColLabelValue(1, _("Price per Page")) |
213 | | self.SetColLabelValue(2, _("Price per Job")) |
214 | | attr = gridlib.GridCellAttr() |
215 | | attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) |
216 | | attr.SetReadOnly(True) |
217 | | colour = wx.GREEN |
218 | | if user.Balance <= 0.0 : |
219 | | colour = wx.RED |
220 | | attr.SetBackgroundColour(colour) |
221 | | self.SetColAttr(0, attr) |
222 | | attr = gridlib.GridCellAttr() |
223 | | attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) |
224 | | attr.SetReadOnly(True) |
225 | | self.SetColAttr(1, attr) |
226 | | self.SetColAttr(2, attr) |
227 | | i = 0 |
228 | | for printername in user.Printers.keys() : |
229 | | printer = user.Printers[printername] |
230 | | quota = user.Quotas[printername] |
231 | | self.SetRowLabelValue(i, printername) |
232 | | self.SetCellValue(i, 0, str(quota.PageCounter)) |
233 | | self.SetCellValue(i, 1, str(printer.PricePerPage)) |
234 | | self.SetCellValue(i, 2, str(printer.PricePerJob)) |
235 | | i += 1 |
236 | | self.SetRowLabelValue(i, "") |
237 | | self.SetCellValue(i, 0, _("CREDITS :") + (" %.2f" % user.Balance)) |
238 | | self.SetCellAlignment(i, 0, wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) |
239 | | else : |
240 | | self.SetColLabelValue(0, _("Page Counter")) |
241 | | self.SetColLabelValue(1, _("Soft Limit")) |
242 | | self.SetColLabelValue(2, _("Hard Limit")) |
243 | | self.SetColLabelValue(3, _("Date Limit")) |
244 | | attr = gridlib.GridCellAttr() |
245 | | attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) |
246 | | attr.SetReadOnly(True) |
247 | | self.SetColAttr(0, attr) |
248 | | attr = gridlib.GridCellAttr() |
249 | | attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) |
250 | | attr.SetReadOnly(True) |
251 | | self.SetColAttr(1, attr) |
252 | | self.SetColAttr(2, attr) |
253 | | self.SetColAttr(3, attr) |
254 | | i = 0 |
255 | | for printername in user.Quotas.keys() : |
256 | | quota = user.Quotas[printername] |
257 | | self.SetRowLabelValue(i, printername) |
258 | | self.SetCellValue(i, 0, str(quota.PageCounter)) |
259 | | self.SetCellValue(i, 1, str(quota.SoftLimit)) |
260 | | self.SetCellValue(i, 2, str(quota.HardLimit)) |
261 | | self.SetCellValue(i, 3, str(quota.DateLimit)) |
262 | | colour = wx.GREEN |
263 | | if quota.SoftLimit is not None : |
264 | | if quota.PageCounter >= quota.SoftLimit : |
265 | | colour = wx.RED |
266 | | elif quota.HardLimit is not None : |
267 | | if quota.PageCounter >= quota.HardLimit : |
268 | | colour = wx.RED |
269 | | self.SetCellBackgroundColour(i, 0, colour) |
270 | | i += 1 |
271 | | self.AutoSize() |
272 | | self.Refresh() |
273 | | |
274 | | class PyKotIcon(wxPython.wx.wxFrame): |
| 68 | class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) : |
| 69 | """My own server class.""" |
| 70 | def __init__(self, frame, printserver, localport) : |
| 71 | myIPAddress = socket.gethostbyaddr(socket.gethostname())[2][0] |
| 72 | SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, (myIPAddress, localport)) |
| 73 | self.printServer = printserver |
| 74 | self.frame = frame |
| 75 | loop = threading.Thread(target=self.mainloop) |
| 76 | loop.start() |
| 77 | |
| 78 | def export_quitApplication(self) : |
| 79 | """Makes the application quit.""" |
| 80 | self.frame.quitEvent.set() |
| 81 | return True |
| 82 | |
| 83 | def export_openDialog(self) : |
| 84 | """Opens a dialog to ask username, password, etc...""" |
| 85 | print "Open dialog !" |
| 86 | return ("jerome", "blah") |
| 87 | |
| 88 | def verify_request(self, request, client_address) : |
| 89 | """Ensures that requests which don't come from the print server are rejected.""" |
| 90 | (client, port) = client_address |
| 91 | if socket.gethostbyname(self.printServer) == client : |
| 92 | return True |
| 93 | else : |
| 94 | # Unauthorized access ! |
| 95 | return False |
| 96 | |
| 97 | def _dispatch(self, method, params) : |
| 98 | """Ensure that only export_* methods are available.""" |
| 99 | return getattr(self, "export_%s" % method)(*params) |
| 100 | |
| 101 | def mainloop(self) : |
| 102 | """XML-RPC Server's main loop.""" |
| 103 | self.register_function(self.export_openDialog) |
| 104 | self.register_function(self.export_quitApplication) |
| 105 | while not self.frame.quitEvent.isSet() : |
| 106 | self.handle_request() |
| 107 | self.frame.Close() |
| 108 | sys.exit(0) |
| 109 | |
| 110 | class PyKotIcon(wx.Frame): |
314 | | self.TBTIMER = wx.NewId() |
315 | | self.chrono = wxPython.wx.wxTimer(self, self.TBTIMER) |
316 | | wxPython.wx.EVT_TIMER(self, self.TBTIMER, self.OnChronoTimer) |
317 | | |
318 | | def postInit(self, configFile) : |
319 | | """Loads the configuration file and starts processing.""" |
320 | | self.User = None |
321 | | url = None |
322 | | try : |
323 | | # try a file on disk first |
324 | | u = open(configFile) |
325 | | except IOError : |
326 | | try : |
327 | | # then try through the web |
328 | | u = urllib2.urlopen(configFile) |
329 | | except (ValueError, IOError), msg : |
330 | | raise IOError, _("Impossible to read configuration file %s : %s") % (configFile, msg) |
331 | | url = u.readline().strip() |
332 | | u.close() |
333 | | |
334 | | if not url : |
335 | | raise ValueError, _("Configuration file %s is incorrect.") % configFile |
336 | | self.networkInterface = CGINetworkInterface(url) |
337 | | self.inTimer = False |
338 | | self.chrono.Start(250) # first time in 0.25 second |
339 | | |
340 | | def OnChronoTimer(self, event) : |
341 | | """Retrieves user's data quota information.""" |
342 | | # we stop it there, needed because we want to |
343 | | # change the delay the first time. |
344 | | self.chrono.Stop() |
345 | | if self.inTimer is False : # avoids re-entrance |
346 | | self.inTimer = True |
347 | | try : |
348 | | self.User = self.networkInterface.getUserInfo(getCurrentUserName()) |
349 | | except IOError, msg : |
350 | | sys.stderr.write("ERROR : %s\n" % msg) |
351 | | else : |
352 | | if self.User.LimitBy == "balance" : |
353 | | if self.User.Balance <= 0.0 : |
354 | | self.SetIcon(self.redicon) |
355 | | if self.tbicon is not None : |
356 | | self.tbicon.SetIcon(self.redicon, "PyKotIcon") |
357 | | else : |
358 | | self.SetIcon(self.greenicon) |
359 | | if self.tbicon is not None : |
360 | | self.tbicon.SetIcon(self.greenicon, "PyKotIcon") |
361 | | else : |
362 | | isRed = False |
363 | | for q in self.User.Quotas.keys() : |
364 | | quota = self.User.Quotas[q] |
365 | | if quota.SoftLimit is not None : |
366 | | if quota.PageCounter >= quota.SoftLimit : |
367 | | isRed = True |
368 | | break |
369 | | elif quota.HardLimit is not None : |
370 | | if quota.PageCounter >= quota.HardLimit : |
371 | | isRed = True |
372 | | break |
373 | | if isRed is True : |
374 | | self.SetIcon(self.redicon) |
375 | | if self.tbicon is not None : |
376 | | self.tbicon.SetIcon(self.redicon, "PyKotIcon") |
377 | | else : |
378 | | self.SetIcon(self.greenicon) |
379 | | if self.tbicon is not None : |
380 | | self.tbicon.SetIcon(self.greenicon, "PyKotIcon") |
381 | | if hasattr(self, "quotasgrid") : |
382 | | self.quotasgrid.Close() |
383 | | self.quotasgrid.Destroy() |
384 | | del self.quotasgrid |
385 | | self.quotasgrid = PyKotIconGrid(self, self.User) |
386 | | self.Refresh() |
387 | | self.inTimer = False |
388 | | # Now we want it every 3 minutes |
389 | | self.chrono.Start(1000 * 60 * 3) # every 3 minutes |
| 150 | def postInit(self, printserver, localport) : |
| 151 | """Starts the XML-RPC server.""" |
| 152 | self.quitEvent = threading.Event() |
| 153 | self.server = MyXMLRPCServer(self, printserver, localport) |