#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # PyKotIcon - Windows System Tray Icon for PyKota # # (c) 2003-2004 Jerome Alet # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # # $Id$ # # import sys import os import urllib import urllib2 import locale import gettext if sys.platform == "win32" : isWindows = 1 try : import win32api except ImportError : raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them." else : isWindows = 0 import pwd try : import wxPython.wx import wx import wx.grid as gridlib hasWxPython = 1 except ImportError : hasWxPython = 0 raise ImportError, "wxPython is missing. Please install it." DUMPYKOTA_URL = "http://cgi.librelogiciel.com/cgi-bin/dumpykota.cgi" def getCurrentUserName() : """Retrieves the current user's name.""" if isWindows : return win32api.GetUserName() else : try : return pwd.getpwuid(os.geteuid())[0] except : return "** Unknown **" class Printer : """A class for PyKota Printers.""" def __init__(self, printername, priceperpage, priceperjob): """Initialize printer datas.""" self.PrinterName = printername try : self.PricePerPage = float(priceperpage) except (ValueError, TypeError) : self.PricePerPage = 0.0 try : self.PricePerJob = float(priceperjob) except (ValueError, TypeError) : self.PricePerJob = 0.0 class UserQuota : """A class for PyKota User Print Quota entries.""" def __init__(self, printername, pagecount, softlimit, hardlimit, datelimit): """Initialize user print quota datas.""" self.PrinterName = printername try : self.PageCounter = int(pagecount) except (ValueError, TypeError) : self.PageCounter = 0 try : self.SoftLimit = int(softlimit) except (ValueError, TypeError) : self.SoftLimit = None try : self.HardLimit = int(hardlimit) except (ValueError, TypeError) : self.HardLimit = None self.DateLimit = datelimit class User : """A class for PyKota users.""" def __init__(self, username, userinfo, userquotas, printers) : """Initialize user datas.""" self.UserName = username self.LimitBy = userinfo["limitby"][0].lower() try : self.Balance = float(userinfo["balance"][0]) except (ValueError, TypeError) : self.Balance = 0.0 try : self.LifeTimePaid = float(userinfo["lifetimepaid"][0]) except (ValueError, TypeError) : self.LifeTimePaid = 0.0 self.Printers = {} self.Quotas = {} for i in range(len(userquotas["printername"])) : printername = userquotas["printername"][i] try : pindex = printers["printername"].index(printername) except (ValueError, KeyError) : pass else : self.Printers[printername] = Printer(printername, \ printers["priceperpage"][pindex],\ printers["priceperjob"][pindex]) pagecounter = userquotas["pagecounter"][i] softlimit = userquotas["softlimit"][i] hardlimit = userquotas["hardlimit"][i] datelimit = userquotas["datelimit"][i] self.Quotas[printername] = UserQuota(printername, pagecounter, \ softlimit, hardlimit, \ datelimit) class CGINetworkInterface : """A class for all network interactions.""" def __init__(self, cgiurl, authname=None, authpw=None) : """Initialize CGI connection datas.""" self.cgiurl = cgiurl self.authname = authname self.authpw = authpw def retrieveDatas(self, arguments, fieldnames) : """Retrieve datas from the CGI script.""" args = { "report" : 1, "format" : "csv", } args.update(arguments) answer = {} try : url = "%s?%s" % (self.cgiurl, urllib.urlencode(args)) u = urllib2.urlopen(url) lines = u.readlines() except IOError, msg : raise IOError, _("Unable to retrieve %s : %s") % (url, msg) else : u.close() try : lines = [ line.strip().split(",") for line in lines ] fields = [field[1:-1] for field in lines[0]] indices = [fields.index(fname) for fname in fieldnames] answer = dict([ (fieldname, \ [ line[fields.index(fieldname)][1:-1] \ for line in lines[1:] ]) \ for fieldname in fieldnames ]) except : raise ValueError, _("Invalid datas retrieved from %s") % url return answer def getPrinters(self) : """Retrieve the printer's names.""" arguments = { "report" : 1, "format" : "csv", "datatype" : "printers", } return self.retrieveDatas(arguments, ["printername", "priceperpage", \ "priceperjob"]) def getUser(self, username) : """Retrieve the user's information.""" arguments = { "datatype" : "users", "filter" : "username=%s" % username, } return self.retrieveDatas(arguments, ("limitby", "balance", \ "lifetimepaid")) def getUserPQuotas(self, username) : """Retrieve the user's print quota information.""" arguments = { "datatype" : "upquotas", "filter" : "username=%s" % username, } return self.retrieveDatas(arguments, ("printername", "pagecounter", \ "softlimit", "hardlimit", \ "datelimit")) def getUserInfo(self, username) : """Retrieves the user account and quota information.""" info = self.getUser(username) if info : quotas = self.getUserPQuotas(username) return User(username, info, \ self.getUserPQuotas(username), \ self.getPrinters()) class PyKotIconGrid(gridlib.Grid) : """A class for user print quota entries.""" def __init__(self, parent, user) : gridlib.Grid.__init__(self, parent, -1) nbrows = len(user.Quotas.keys()) nbcols = 4 if user.LimitBy == "balance" : nbrows += 1 nbcols -= 1 self.CreateGrid(nbrows, nbcols) self.EnableEditing(False) if user.LimitBy == "balance" : self.SetColLabelValue(0, _("Page Counter")) self.SetColLabelValue(1, _("Price per Page")) self.SetColLabelValue(2, _("Price per Job")) attr = gridlib.GridCellAttr() attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) attr.SetReadOnly(True) colour = wx.GREEN if user.Balance <= 0.0 : colour = wx.RED attr.SetBackgroundColour(colour) self.SetColAttr(0, attr) attr = gridlib.GridCellAttr() attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) attr.SetReadOnly(True) self.SetColAttr(1, attr) self.SetColAttr(2, attr) i = 0 for printername in user.Printers.keys() : printer = user.Printers[printername] quota = user.Quotas[printername] self.SetRowLabelValue(i, printername) self.SetCellValue(i, 0, str(quota.PageCounter)) self.SetCellValue(i, 1, str(printer.PricePerPage)) self.SetCellValue(i, 2, str(printer.PricePerJob)) i += 1 self.SetRowLabelValue(i, "") self.SetCellValue(i, 0, _("CREDITS :") + (" %.2f" % user.Balance)) self.SetCellAlignment(i, 0, wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) else : self.SetColLabelValue(0, _("Page Counter")) self.SetColLabelValue(1, _("Soft Limit")) self.SetColLabelValue(2, _("Hard Limit")) self.SetColLabelValue(3, _("Date Limit")) attr = gridlib.GridCellAttr() attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) attr.SetReadOnly(True) self.SetColAttr(0, attr) attr = gridlib.GridCellAttr() attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) attr.SetReadOnly(True) self.SetColAttr(1, attr) self.SetColAttr(2, attr) self.SetColAttr(3, attr) i = 0 for printername in user.Quotas.keys() : quota = user.Quotas[printername] self.SetRowLabelValue(i, printername) self.SetCellValue(i, 0, str(quota.PageCounter)) self.SetCellValue(i, 1, str(quota.SoftLimit)) self.SetCellValue(i, 2, str(quota.HardLimit)) self.SetCellValue(i, 3, str(quota.DateLimit)) colour = wx.GREEN if quota.SoftLimit is not None : if quota.PageCounter >= quota.SoftLimit : colour = wx.RED elif quota.HardLimit is not None : if quota.PageCounter >= quota.HardLimit : colour = wx.RED self.SetCellBackgroundColour(i, 0, colour) i += 1 self.AutoSize() class PyKotIcon(wxPython.wx.wxFrame): """Main class.""" def __init__(self, parent, id): wxPython.wx.wxFrame.__init__(self, parent, -1, \ _("PyKota Print Quota for user %s") % getCurrentUserName(), \ size = (460, -1), \ style = wxPython.wx.wxDEFAULT_FRAME_STYLE \ | wxPython.wx.wxSIZE_AUTO_HEIGHT \ | wxPython.wx.wxSIZE_AUTO_WIDTH \ | wxPython.wx.wxICONIZE \ | wxPython.wx.wxNO_FULL_REPAINT_ON_RESIZE) self.tbicon = wxPython.wx.wxTaskBarIcon() self.greenicon = wxPython.wx.wxIcon(os.path.join("..", "icons", "pykoticon-green.ico"), \ wxPython.wx.wxBITMAP_TYPE_ICO) self.redicon = wxPython.wx.wxIcon(os.path.join("..", "icons", "pykoticon-red.ico"), \ wxPython.wx.wxBITMAP_TYPE_ICO) self.SetIcon(self.greenicon) self.tbicon.SetIcon(self.greenicon, "PyKotIcon") wxPython.wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate) wxPython.wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu) wxPython.wx.EVT_ICONIZE(self, self.OnIconify) self.TBMENU_RESTORE = wx.NewId() self.TBMENU_CLOSE = wx.NewId() wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \ self.OnTaskBarActivate) wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \ self.OnTaskBarClose) self.menu = wxPython.wx.wxMenu() self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota")) self.menu.Append(self.TBMENU_CLOSE, _("Quit")) wxPython.wx.EVT_CLOSE(self, self.OnClose) self.TBTIMER = wx.NewId() self.chrono = wxPython.wx.wxTimer(self, self.TBTIMER) wxPython.wx.EVT_TIMER(self, self.TBTIMER, self.OnChronoTimer) self.User = None self.networkInterface = CGINetworkInterface(DUMPYKOTA_URL) self.inTimer = False self.chrono.Start(250) # first time in 0.25 second def OnChronoTimer(self, event) : """Retrieves user's data quota information.""" # we stop it there, needed because we want to # change the delay the first time. self.chrono.Stop() if self.inTimer is False : # avoids re-entrance self.inTimer = True self.User = self.networkInterface.getUserInfo(getCurrentUserName()) if self.User.LimitBy == "balance" : if self.User.Balance <= 0.0 : self.SetIcon(self.redicon) self.tbicon.SetIcon(self.redicon, "PyKotIcon") else : self.SetIcon(self.greenicon) self.tbicon.SetIcon(self.greenicon, "PyKotIcon") else : isRed = False for q in self.User.Quotas.keys() : quota = self.User.Quotas[q] if quota.SoftLimit is not None : if quota.PageCounter >= quota.SoftLimit : isRed = True break elif quota.HardLimit is not None : if quota.PageCounter >= quota.HardLimit : isRed = True break if isRed is True : self.SetIcon(self.redicon) self.tbicon.SetIcon(self.redicon, "PyKotIcon") else : self.SetIcon(self.greenicon) self.tbicon.SetIcon(self.greenicon, "PyKotIcon") if hasattr(self, "quotasgrid") : self.quotasgrid.Close() self.quotasgrid.Destroy() del self.quotasgrid self.quotasgrid = PyKotIconGrid(self, self.User) self.inTimer = False # Now we want it every 3 minutes #self.chrono.Start(1000 * 60 * 3) # every 3 minutes self.chrono.Start(1000 * 20) # every 20 seconds def OnIconify(self, event) : self.Hide() def OnTaskBarActivate(self, event) : if self.IsIconized() : self.Iconize(False) if not self.IsShown() : self.Show(True) self.Raise() def OnClose(self, event) : if hasattr(self, "chrono") : self.chrono.Stop() del self.chrono if hasattr(self, "quotasgrid") : self.quotasgrid.Close() self.quotasgrid.Destroy() del self.quotasgrid if hasattr(self, "menu") : self.menu.Destroy() del self.menu if hasattr(self, "tbicon") : self.tbicon.Destroy() del self.tbicon self.Destroy() def OnTaskBarMenu(self, evt) : self.tbicon.PopupMenu(self.menu) def OnTaskBarClose(self, evt) : self.Close() #wxPython.wx.wxGetApp().ProcessIdle() class PyKotIconApp(wx.PySimpleApp): def OnInit(self) : try : frame = PyKotIcon(None, -1) except : crashed() else : frame.Show(True) return True def main(): """Program's entry point.""" try : locale.setlocale(locale.LC_ALL, "") except (locale.Error, IOError) : sys.stderr.write("Problem while setting locale.\n") try : gettext.install("pykoticon") except : gettext.NullTranslations().install() app = PyKotIconApp() app.MainLoop() def crashed() : """Minimal crash method.""" import traceback lines = [] for line in traceback.format_exception(*sys.exc_info()) : lines.extend([l for l in line.split("\n") if l]) msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)]) sys.stderr.write(msg) sys.stderr.flush() def test() : """Runs in test mode (console).""" if len(sys.argv) >= 3 : username = sys.argv[2] else : username = getCurrentUserName() net = CGINetworkInterface(DUMPYKOTA_URL) user = net.getUserInfo(username) print "UserName : ", user.UserName print "LimitBy : ", user.LimitBy print "Balance : ", user.Balance for printername in user.Quotas.keys() : quota = user.Quotas[printername] print "\tPrinterName : ", printername print "\tPageCounter : ", quota.PageCounter print "\tSoftLimit : ", quota.SoftLimit print "\tHardLimit : ", quota.HardLimit print "\tDateLimit : ", quota.DateLimit print for printername in user.Printers.keys() : printer = user.Printers[printername] print "\tPrinterName : ", printername print "\tPrice per Page : ", printer.PricePerPage print "\tPrice per Job : ", printer.PricePerJob print if __name__ == '__main__': if (len(sys.argv) >= 2) and (sys.argv[1] == "--test") : test() else : main()