root / pykoticon / trunk / bin / pykoticon @ 82

Revision 82, 19.5 kB (checked in by jerome, 19 years ago)

Nothing special

  • Property svn:keywords set to Id
RevLine 
[47]1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
[48]4# PyKotIcon - Windows System Tray Icon for PyKota
[47]5#
6# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
[53]21# $Id$
[47]22#
23#
24
25import sys
[63]26import os
[47]27import urllib
28import urllib2
[65]29import locale
30import gettext
[47]31
[65]32if sys.platform == "win32" :
33    isWindows = 1
34    try :
35        import win32api
36    except ImportError :   
37        raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them."
[76]38    else :   
39        iconsdir = "."
[65]40else :       
41    isWindows = 0
[76]42    iconsdir = "/usr/share/pykoticon"   # TODO : change this
[65]43    import pwd
[57]44   
[58]45try :   
46    import wxPython.wx
47    import wx
[63]48    import wx.grid as gridlib
[58]49    hasWxPython = 1
50except ImportError :   
51    hasWxPython = 0
[75]52    raise ImportError, "wxPython is missing. Please install it."
[58]53   
[65]54def getCurrentUserName() :
55    """Retrieves the current user's name."""
56    if isWindows :
57        return win32api.GetUserName()
58    else :   
59        try :
60            return pwd.getpwuid(os.geteuid())[0]
61        except :
62            return "** Unknown **"
63       
64class 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
[56]78class 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       
97class User :
98    """A class for PyKota users."""
[66]99    def __init__(self, username, userinfo, userquotas, printers) :
[56]100        """Initialize user datas."""
[65]101        self.UserName = username
[56]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
[66]111        self.Printers = {}   
112        self.Quotas = {}
[56]113        for i in range(len(userquotas["printername"])) :
114            printername = userquotas["printername"][i]
[66]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])
[56]123            pagecounter = userquotas["pagecounter"][i]
124            softlimit = userquotas["softlimit"][i]
125            hardlimit = userquotas["hardlimit"][i]
126            datelimit = userquotas["datelimit"][i]
[66]127            self.Quotas[printername] = UserQuota(printername, pagecounter, \
128                                                   softlimit, hardlimit, \
129                                                              datelimit)
[56]130           
[55]131class CGINetworkInterface :
132    """A class for all network interactions."""
[56]133    def __init__(self, cgiurl, authname=None, authpw=None) :
[55]134        """Initialize CGI connection datas."""
[56]135        self.cgiurl = cgiurl
[81]136        self.authname = authname        # TODO : at least do basic auth
[56]137        self.authpw = authpw
[55]138       
139    def retrieveDatas(self, arguments, fieldnames) :
140        """Retrieve datas from the CGI script."""
141        args = { "report" : 1,
142                 "format" : "csv",
143               } 
144        args.update(arguments)           
145        answer = {}
146        try :           
[56]147            url = "%s?%s" % (self.cgiurl, urllib.urlencode(args))
[55]148            u = urllib2.urlopen(url)
149            lines = u.readlines()
150        except IOError, msg :   
[65]151            raise IOError, _("Unable to retrieve %s : %s") % (url, msg)
[55]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, \
[56]159                                  [ line[fields.index(fieldname)][1:-1] \
160                                    for line in lines[1:] ]) \
[55]161                                for fieldname in fieldnames ])
162            except :   
[65]163                raise ValueError, _("Invalid datas retrieved from %s") % url
[55]164        return answer
165       
[66]166    def getPrinters(self) :
[55]167        """Retrieve the printer's names."""
168        arguments = { "report" : 1,
169                      "format" : "csv",
170                      "datatype" : "printers",
171                    } 
[66]172        return self.retrieveDatas(arguments, ["printername", "priceperpage", \
173                                              "priceperjob"])
[55]174       
[66]175    def getUser(self, username) :
[55]176        """Retrieve the user's information."""
177        arguments = { "datatype" : "users",
178                      "filter" : "username=%s" % username,
179                    } 
[58]180        return self.retrieveDatas(arguments, ("limitby", "balance", \
181                                                         "lifetimepaid"))
[55]182       
183    def getUserPQuotas(self, username) :
184        """Retrieve the user's print quota information."""
185        arguments = { "datatype" : "upquotas",
186                      "filter" : "username=%s" % username,
187                    } 
[56]188        return self.retrieveDatas(arguments, ("printername", "pagecounter", \
[58]189                                              "softlimit", "hardlimit", \
190                                              "datelimit"))
[56]191                                             
[66]192    def getUserInfo(self, username) :
[56]193        """Retrieves the user account and quota information."""
[66]194        info = self.getUser(username)
[60]195        if info :
196            quotas = self.getUserPQuotas(username)
[66]197            return User(username, info, \
198                                  self.getUserPQuotas(username), \
199                                  self.getPrinters())
[47]200   
[63]201class PyKotIconGrid(gridlib.Grid) :   
202    """A class for user print quota entries."""
[66]203    def __init__(self, parent, user) :
[63]204        gridlib.Grid.__init__(self, parent, -1)
[75]205        nbrows = len(user.Quotas.keys())
206        nbcols = 4
[67]207        if user.LimitBy == "balance" :
[75]208            nbrows += 1
209            nbcols -= 1
210        self.CreateGrid(nbrows, nbcols)
[63]211        self.EnableEditing(False)
[67]212        if user.LimitBy == "balance" :
213            self.SetColLabelValue(0, _("Page Counter"))
214            self.SetColLabelValue(1, _("Price per Page"))
215            self.SetColLabelValue(2, _("Price per Job"))
[63]216            attr = gridlib.GridCellAttr()
[67]217            attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
218            attr.SetReadOnly(True)
[63]219            colour = wx.GREEN
[67]220            if user.Balance <= 0.0 :
221                colour = wx.RED
[63]222            attr.SetBackgroundColour(colour)       
[67]223            self.SetColAttr(0, attr)
224            attr = gridlib.GridCellAttr()
225            attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
226            attr.SetReadOnly(True)
227            self.SetColAttr(1, attr)
228            self.SetColAttr(2, attr)
229            i = 0
230            for printername in user.Printers.keys() :
231                printer = user.Printers[printername]
232                quota = user.Quotas[printername]
233                self.SetRowLabelValue(i, printername)
234                self.SetCellValue(i, 0, str(quota.PageCounter))
235                self.SetCellValue(i, 1, str(printer.PricePerPage))
236                self.SetCellValue(i, 2, str(printer.PricePerJob))
237                i += 1
[75]238            self.SetRowLabelValue(i, "")
239            self.SetCellValue(i, 0, _("CREDITS :") + (" %.2f" % user.Balance))
240            self.SetCellAlignment(i, 0, wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
[67]241        else :   
242            self.SetColLabelValue(0, _("Page Counter"))
243            self.SetColLabelValue(1, _("Soft Limit"))
244            self.SetColLabelValue(2, _("Hard Limit"))
245            self.SetColLabelValue(3, _("Date Limit"))
246            attr = gridlib.GridCellAttr()
247            attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
248            attr.SetReadOnly(True)
249            self.SetColAttr(0, attr)
250            attr = gridlib.GridCellAttr()
251            attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
252            attr.SetReadOnly(True)
253            self.SetColAttr(1, attr)
254            self.SetColAttr(2, attr)
255            self.SetColAttr(3, attr)
256            i = 0
257            for printername in user.Quotas.keys() :
258                quota = user.Quotas[printername]
259                self.SetRowLabelValue(i, printername)
260                self.SetCellValue(i, 0, str(quota.PageCounter))
261                self.SetCellValue(i, 1, str(quota.SoftLimit))
262                self.SetCellValue(i, 2, str(quota.HardLimit))
263                self.SetCellValue(i, 3, str(quota.DateLimit))
264                colour = wx.GREEN
265                if quota.SoftLimit is not None :
266                    if quota.PageCounter >= quota.SoftLimit :
267                        colour = wx.RED
268                elif quota.HardLimit is not None :       
269                    if quota.PageCounter >= quota.HardLimit :
270                        colour = wx.RED
271                self.SetCellBackgroundColour(i, 0, colour)       
272                i += 1
[65]273        self.AutoSize()
[80]274        self.Refresh()
[63]275           
[58]276class PyKotIcon(wxPython.wx.wxFrame):
[63]277    """Main class."""
278    def __init__(self, parent, id):
279        wxPython.wx.wxFrame.__init__(self, parent, -1, \
[65]280               _("PyKota Print Quota for user %s") % getCurrentUserName(), \
281               size = (460, -1), \
[66]282               style = wxPython.wx.wxDEFAULT_FRAME_STYLE \
[72]283                     | wxPython.wx.wxSIZE_AUTO_HEIGHT \
284                     | wxPython.wx.wxSIZE_AUTO_WIDTH \
285                     | wxPython.wx.wxICONIZE \
[66]286                     | wxPython.wx.wxNO_FULL_REPAINT_ON_RESIZE)
[79]287        try :             
288            self.tbicon = wxPython.wx.wxTaskBarIcon()
289        except AttributeError :   
290            self.tbicon = None # No taskbar icon facility, old wxWidgets maybe
[72]291       
[76]292        self.greenicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-green.ico"), \
[58]293                                  wxPython.wx.wxBITMAP_TYPE_ICO)
[76]294        self.redicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-red.ico"), \
[63]295                                  wxPython.wx.wxBITMAP_TYPE_ICO)
[72]296       
[63]297        self.SetIcon(self.greenicon)
[79]298        if self.tbicon is not None :
299            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
300            wxPython.wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
301            wxPython.wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
[63]302       
[79]303            self.TBMENU_RESTORE = wx.NewId()
304            self.TBMENU_CLOSE = wx.NewId()
305            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
306                                              self.OnTaskBarActivate)
307            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
308                                              self.OnTaskBarClose)
309            self.menu = wxPython.wx.wxMenu()
310            self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
311            self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
312       
[58]313        wxPython.wx.EVT_ICONIZE(self, self.OnIconify)
[64]314        wxPython.wx.EVT_CLOSE(self, self.OnClose)
315       
[59]316        self.TBTIMER = wx.NewId()
[63]317        self.chrono = wxPython.wx.wxTimer(self, self.TBTIMER)
318        wxPython.wx.EVT_TIMER(self, self.TBTIMER, self.OnChronoTimer)
[59]319       
[81]320    def postInit(self, configFile) :   
321        """Loads the configuration file and starts processing."""
[59]322        self.User = None
[81]323        url = None
324        try :
325            # try a file on disk first
326            u = open(configFile)
327        except IOError :   
328            try :
329                # then try through the web
330                u = urllib2.urlopen(configFile)
331            except (ValueError, IOError), msg :   
332                raise IOError, _("Impossible to read configuration file %s : %s") % (configFile, msg)
333        url = u.readline().strip()
334        u.close()
335       
336        if not url :
337            raise ValueError, _("Configuration file %s is incorrect.") % configFile
338        self.networkInterface = CGINetworkInterface(url)
[63]339        self.inTimer = False
[65]340        self.chrono.Start(250) # first time in 0.25 second
[58]341
[59]342    def OnChronoTimer(self, event) :
343        """Retrieves user's data quota information."""
[63]344        # we stop it there, needed because we want to
345        # change the delay the first time.
346        self.chrono.Stop()   
347        if self.inTimer is False : # avoids re-entrance
348            self.inTimer = True
[81]349            try :
350                self.User = self.networkInterface.getUserInfo(getCurrentUserName())
351            except IOError, msg :   
352                sys.stderr.write("ERROR : %s\n" % msg)
353            else :
354                if self.User.LimitBy == "balance" :
355                    if self.User.Balance <= 0.0 :
356                        self.SetIcon(self.redicon)
357                        if self.tbicon is not None :
358                            self.tbicon.SetIcon(self.redicon, "PyKotIcon")
359                    else :   
360                        self.SetIcon(self.greenicon)
361                        if self.tbicon is not None :
362                            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
363                else :       
364                    isRed = False
365                    for q in self.User.Quotas.keys() :
366                        quota = self.User.Quotas[q]
367                        if quota.SoftLimit is not None :
368                            if quota.PageCounter >= quota.SoftLimit :
369                                isRed = True
370                                break
371                        elif quota.HardLimit is not None :       
372                            if quota.PageCounter >= quota.HardLimit :
373                                isRed = True
374                                break
375                    if isRed is True :
376                        self.SetIcon(self.redicon)
377                        if self.tbicon is not None :
378                            self.tbicon.SetIcon(self.redicon, "PyKotIcon")
379                    else :   
380                        self.SetIcon(self.greenicon)
381                        if self.tbicon is not None :
382                            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
383                if hasattr(self, "quotasgrid") :   
384                    self.quotasgrid.Close()
385                    self.quotasgrid.Destroy()
386                    del self.quotasgrid
387                self.quotasgrid = PyKotIconGrid(self, self.User)   
388                self.Refresh()
[63]389            self.inTimer = False
390        # Now we want it every 3 minutes   
[64]391        #self.chrono.Start(1000 * 60 * 3) # every 3 minutes
392        self.chrono.Start(1000 * 20) # every 20 seconds
[59]393   
394    def OnIconify(self, event) :
[72]395        self.Hide()
[58]396
[59]397    def OnTaskBarActivate(self, event) :
398        if self.IsIconized() :
[58]399            self.Iconize(False)
[59]400        if not self.IsShown() :
[58]401            self.Show(True)
402        self.Raise()
403
[64]404    def OnClose(self, event) :
[59]405        if hasattr(self, "chrono") :
406            self.chrono.Stop()
407            del self.chrono
[66]408        if hasattr(self, "quotasgrid") :
409            self.quotasgrid.Close()
410            self.quotasgrid.Destroy()
411            del self.quotasgrid
[58]412        if hasattr(self, "menu") :
[64]413            self.menu.Destroy()
[58]414            del self.menu
[59]415        if hasattr(self, "tbicon") :
[58]416            self.tbicon.Destroy()
417            del self.tbicon
418        self.Destroy()
419
[59]420    def OnTaskBarMenu(self, evt) :
[58]421        self.tbicon.PopupMenu(self.menu)
422
[59]423    def OnTaskBarClose(self, evt) :
[58]424        self.Close()
425
[72]426class PyKotIconApp(wx.PySimpleApp):
[58]427    def OnInit(self) :
[63]428        try :
[81]429            self.frame = PyKotIcon(None, -1)
[63]430        except :   
431            crashed()
[58]432        return True
433       
[81]434    def postInit(self, configFile) :   
435        """Continues processing."""
436        self.frame.postInit(configFile)
437        self.frame.Show(True)
438       
439def main(conffile):
[63]440    """Program's entry point."""
[65]441    try :
442        locale.setlocale(locale.LC_ALL, "")
443    except (locale.Error, IOError) :
444        sys.stderr.write("Problem while setting locale.\n")
445    try :
446        gettext.install("pykoticon")
447    except :
448        gettext.NullTranslations().install()
[63]449    app = PyKotIconApp()
[81]450    app.postInit(conffile)
[63]451    app.MainLoop()
452   
[58]453def crashed() :   
454    """Minimal crash method."""
455    import traceback
456    lines = []
457    for line in traceback.format_exception(*sys.exc_info()) :
458        lines.extend([l for l in line.split("\n") if l])
459    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
460    sys.stderr.write(msg)
461    sys.stderr.flush()
[47]462   
[81]463# def test() :
464#     """Runs in test mode (console)."""
465#     print "Configuration file is ignored."
466#     if len(sys.argv) >= 3 :
467#         username = sys.argv[2]
468#     else :   
469#         username = getCurrentUserName()
470#     net = CGINetworkInterface(DUMPYKOTA_URL)
471#     user = net.getUserInfo(username)
472#     print "UserName : ", user.UserName
473#     print "LimitBy : ", user.LimitBy
474#     print "Balance : ", user.Balance
475#     for printername in user.Quotas.keys() :
476#         quota = user.Quotas[printername]
477#         print "\tPrinterName : ", printername
478#         print "\tPageCounter : ", quota.PageCounter
479#         print "\tSoftLimit : ", quota.SoftLimit
480#         print "\tHardLimit : ", quota.HardLimit
481#         print "\tDateLimit : ", quota.DateLimit
482#         print
483#     for printername in user.Printers.keys() :
484#         printer = user.Printers[printername]
485#         print "\tPrinterName : ", printername
486#         print "\tPrice per Page : ", printer.PricePerPage
487#         print "\tPrice per Job : ", printer.PricePerJob
488#         print
[47]489
[58]490if __name__ == '__main__':
[81]491    if len(sys.argv) >= 2 :
492        arg = sys.argv[1]
493        if arg in ("-v", "--version") :   
494            print "0.1"
495        elif arg in ("-h", "--help") :   
496            print "usage : pykoticon configuration_file"
497        #elif arg == "--test" :
498        #    test()
499        else :
500            main(arg)
[58]501    else :   
[81]502        main("pykoticon.conf")
Note: See TracBrowser for help on using the browser.