root / pykoticon / trunk / bin / pykoticon @ 72

Revision 72, 17.4 kB (checked in by jerome, 19 years ago)

Added setup script for py2exe
Fixed (it seems) the segfault

  • 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        import win32net
37        import win32netcon
38    except ImportError :   
39        raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them."
40else :       
41    isWindows = 0
42    import pwd
[57]43   
[58]44try :   
45    import wxPython.wx
46    import wx
[63]47    import wx.grid as gridlib
[58]48    hasWxPython = 1
49except ImportError :   
50    hasWxPython = 0
51    raise ImportError, "wxPython for Windows is missing. Please install it."
52   
[47]53DUMPYKOTA_URL = "http://cgi.librelogiciel.com/cgi-bin/dumpykota.cgi"
54
[65]55def getCurrentUserName() :
56    """Retrieves the current user's name."""
57    if isWindows :
58        return win32api.GetUserName()
59    else :   
60        try :
61            return pwd.getpwuid(os.geteuid())[0]
62        except :
63            return "** Unknown **"
64       
65class Printer :
66    """A class for PyKota Printers."""
67    def __init__(self, printername, priceperpage, priceperjob):
68        """Initialize printer datas."""
69        self.PrinterName = printername
70        try :
71            self.PricePerPage = float(priceperpage)
72        except (ValueError, TypeError) :
73            self.PricePerPage = 0.0
74        try :
75            self.PricePerJob = float(priceperjob)
76        except (ValueError, TypeError) :
77            self.PricePerJob = 0.0
78
[56]79class UserQuota :
80    """A class for PyKota User Print Quota entries."""
81    def __init__(self, printername, pagecount, softlimit, hardlimit, datelimit):
82        """Initialize user print quota datas."""
83        self.PrinterName = printername
84        try :
85            self.PageCounter = int(pagecount)
86        except (ValueError, TypeError) :
87            self.PageCounter = 0
88        try :
89            self.SoftLimit = int(softlimit)
90        except (ValueError, TypeError) :
91            self.SoftLimit = None
92        try :
93            self.HardLimit = int(hardlimit)
94        except (ValueError, TypeError) :
95            self.HardLimit = None
96        self.DateLimit = datelimit
97       
98class User :
99    """A class for PyKota users."""
[66]100    def __init__(self, username, userinfo, userquotas, printers) :
[56]101        """Initialize user datas."""
[65]102        self.UserName = username
[56]103        self.LimitBy = userinfo["limitby"][0].lower()
104        try :
105            self.Balance = float(userinfo["balance"][0])
106        except (ValueError, TypeError) :
107            self.Balance = 0.0
108        try :
109            self.LifeTimePaid = float(userinfo["lifetimepaid"][0])
110        except (ValueError, TypeError) :
111            self.LifeTimePaid = 0.0
[66]112        self.Printers = {}   
113        self.Quotas = {}
[56]114        for i in range(len(userquotas["printername"])) :
115            printername = userquotas["printername"][i]
[66]116            try :
117                pindex = printers["printername"].index(printername)
118            except (ValueError, KeyError) :
119                pass
120            else :   
121                self.Printers[printername] = Printer(printername, \
122                                              printers["priceperpage"][pindex],\
123                                              printers["priceperjob"][pindex])
[56]124            pagecounter = userquotas["pagecounter"][i]
125            softlimit = userquotas["softlimit"][i]
126            hardlimit = userquotas["hardlimit"][i]
127            datelimit = userquotas["datelimit"][i]
[66]128            self.Quotas[printername] = UserQuota(printername, pagecounter, \
129                                                   softlimit, hardlimit, \
130                                                              datelimit)
[56]131           
[55]132class CGINetworkInterface :
133    """A class for all network interactions."""
[56]134    def __init__(self, cgiurl, authname=None, authpw=None) :
[55]135        """Initialize CGI connection datas."""
[56]136        self.cgiurl = cgiurl
137        self.authname = authname
138        self.authpw = authpw
[55]139       
140    def retrieveDatas(self, arguments, fieldnames) :
141        """Retrieve datas from the CGI script."""
142        args = { "report" : 1,
143                 "format" : "csv",
144               } 
145        args.update(arguments)           
146        answer = {}
147        try :           
[56]148            url = "%s?%s" % (self.cgiurl, urllib.urlencode(args))
[55]149            u = urllib2.urlopen(url)
150            lines = u.readlines()
151        except IOError, msg :   
[65]152            raise IOError, _("Unable to retrieve %s : %s") % (url, msg)
[55]153        else :   
154            u.close()
155            try :
156                lines = [ line.strip().split(",") for line in lines ]
157                fields = [field[1:-1] for field in lines[0]]
158                indices = [fields.index(fname) for fname in fieldnames]
159                answer = dict([ (fieldname, \
[56]160                                  [ line[fields.index(fieldname)][1:-1] \
161                                    for line in lines[1:] ]) \
[55]162                                for fieldname in fieldnames ])
163            except :   
[65]164                raise ValueError, _("Invalid datas retrieved from %s") % url
[55]165        return answer
166       
[66]167    def getPrinters(self) :
[55]168        """Retrieve the printer's names."""
169        arguments = { "report" : 1,
170                      "format" : "csv",
171                      "datatype" : "printers",
172                    } 
[66]173        return self.retrieveDatas(arguments, ["printername", "priceperpage", \
174                                              "priceperjob"])
[55]175       
[66]176    def getUser(self, username) :
[55]177        """Retrieve the user's information."""
178        arguments = { "datatype" : "users",
179                      "filter" : "username=%s" % username,
180                    } 
[58]181        return self.retrieveDatas(arguments, ("limitby", "balance", \
182                                                         "lifetimepaid"))
[55]183       
184    def getUserPQuotas(self, username) :
185        """Retrieve the user's print quota information."""
186        arguments = { "datatype" : "upquotas",
187                      "filter" : "username=%s" % username,
188                    } 
[56]189        return self.retrieveDatas(arguments, ("printername", "pagecounter", \
[58]190                                              "softlimit", "hardlimit", \
191                                              "datelimit"))
[56]192                                             
[66]193    def getUserInfo(self, username) :
[56]194        """Retrieves the user account and quota information."""
[66]195        info = self.getUser(username)
[60]196        if info :
197            quotas = self.getUserPQuotas(username)
[66]198            return User(username, info, \
199                                  self.getUserPQuotas(username), \
200                                  self.getPrinters())
[47]201   
[63]202class PyKotIconGrid(gridlib.Grid) :   
203    """A class for user print quota entries."""
[66]204    def __init__(self, parent, user) :
[63]205        gridlib.Grid.__init__(self, parent, -1)
[67]206        if user.LimitBy == "balance" :
207            nbcols = 3
208        else :   
209            nbcols = 4
210        self.CreateGrid(len(user.Quotas.keys()), 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
238        else :   
239            self.SetColLabelValue(0, _("Page Counter"))
240            self.SetColLabelValue(1, _("Soft Limit"))
241            self.SetColLabelValue(2, _("Hard Limit"))
242            self.SetColLabelValue(3, _("Date Limit"))
243            attr = gridlib.GridCellAttr()
244            attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
245            attr.SetReadOnly(True)
246            self.SetColAttr(0, attr)
247            attr = gridlib.GridCellAttr()
248            attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
249            attr.SetReadOnly(True)
250            self.SetColAttr(1, attr)
251            self.SetColAttr(2, attr)
252            self.SetColAttr(3, attr)
253            i = 0
254            for printername in user.Quotas.keys() :
255                quota = user.Quotas[printername]
256                self.SetRowLabelValue(i, printername)
257                self.SetCellValue(i, 0, str(quota.PageCounter))
258                self.SetCellValue(i, 1, str(quota.SoftLimit))
259                self.SetCellValue(i, 2, str(quota.HardLimit))
260                self.SetCellValue(i, 3, str(quota.DateLimit))
261                colour = wx.GREEN
262                if quota.SoftLimit is not None :
263                    if quota.PageCounter >= quota.SoftLimit :
264                        colour = wx.RED
265                elif quota.HardLimit is not None :       
266                    if quota.PageCounter >= quota.HardLimit :
267                        colour = wx.RED
268                self.SetCellBackgroundColour(i, 0, colour)       
269                i += 1
[65]270        self.AutoSize()
[63]271           
[58]272class PyKotIcon(wxPython.wx.wxFrame):
[63]273    """Main class."""
274    def __init__(self, parent, id):
275        wxPython.wx.wxFrame.__init__(self, parent, -1, \
[65]276               _("PyKota Print Quota for user %s") % getCurrentUserName(), \
277               size = (460, -1), \
[66]278               style = wxPython.wx.wxDEFAULT_FRAME_STYLE \
[72]279                     | wxPython.wx.wxSIZE_AUTO_HEIGHT \
280                     | wxPython.wx.wxSIZE_AUTO_WIDTH \
281                     | wxPython.wx.wxICONIZE \
[66]282                     | wxPython.wx.wxNO_FULL_REPAINT_ON_RESIZE)
[72]283        self.tbicon = wxPython.wx.wxTaskBarIcon()
284       
[63]285        self.greenicon = wxPython.wx.wxIcon(os.path.join("..", "icons", "pykoticon-green.ico"), \
[58]286                                  wxPython.wx.wxBITMAP_TYPE_ICO)
[63]287        self.redicon = wxPython.wx.wxIcon(os.path.join("..", "icons", "pykoticon-red.ico"), \
288                                  wxPython.wx.wxBITMAP_TYPE_ICO)
[72]289       
[63]290        self.SetIcon(self.greenicon)
291        self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
292       
[58]293        wxPython.wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
294        wxPython.wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
295        wxPython.wx.EVT_ICONIZE(self, self.OnIconify)
296       
297        self.TBMENU_RESTORE = wx.NewId()
298        self.TBMENU_CLOSE = wx.NewId()
299        wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
300                                          self.OnTaskBarActivate)
301        wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
302                                          self.OnTaskBarClose)
303        self.menu = wxPython.wx.wxMenu()
[65]304        self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
305        self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
[59]306       
[64]307        wxPython.wx.EVT_CLOSE(self, self.OnClose)
308       
[59]309        self.TBTIMER = wx.NewId()
[63]310        self.chrono = wxPython.wx.wxTimer(self, self.TBTIMER)
311        wxPython.wx.EVT_TIMER(self, self.TBTIMER, self.OnChronoTimer)
[59]312       
313        self.User = None
314        self.networkInterface = CGINetworkInterface(DUMPYKOTA_URL)
[63]315        self.inTimer = False
[65]316        self.chrono.Start(250) # first time in 0.25 second
[58]317
[59]318    def OnChronoTimer(self, event) :
319        """Retrieves user's data quota information."""
[63]320        # we stop it there, needed because we want to
321        # change the delay the first time.
322        self.chrono.Stop()   
323        if self.inTimer is False : # avoids re-entrance
324            self.inTimer = True
[66]325            self.User = self.networkInterface.getUserInfo(getCurrentUserName())
[63]326            if self.User.LimitBy == "balance" :
327                if self.User.Balance <= 0.0 :
[72]328                    self.SetIcon(self.redicon)
[63]329                    self.tbicon.SetIcon(self.redicon, "PyKotIcon")
330                else :   
[72]331                    self.SetIcon(self.greenicon)
[63]332                    self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
333            else :       
334                isRed = False
[66]335                for q in self.User.Quotas.keys() :
336                    quota = self.User.Quotas[q]
337                    if quota.SoftLimit is not None :
338                        if quota.PageCounter >= quota.SoftLimit :
[63]339                            isRed = True
340                            break
[66]341                    elif quota.HardLimit is not None :       
342                        if quota.PageCounter >= quota.HardLimit :
[63]343                            isRed = True
344                            break
345                if isRed is True :
[72]346                    self.SetIcon(self.redicon)
[63]347                    self.tbicon.SetIcon(self.redicon, "PyKotIcon")
348                else :   
[72]349                    self.SetIcon(self.greenicon)
[63]350                    self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
[67]351            if hasattr(self, "quotasgrid") :   
352                self.quotasgrid.Close()
353                self.quotasgrid.Destroy()
354                del self.quotasgrid
355            self.quotasgrid = PyKotIconGrid(self, self.User)   
[63]356            self.inTimer = False
357        # Now we want it every 3 minutes   
[64]358        #self.chrono.Start(1000 * 60 * 3) # every 3 minutes
359        self.chrono.Start(1000 * 20) # every 20 seconds
[59]360   
361    def OnIconify(self, event) :
[72]362        self.Hide()
[58]363
[59]364    def OnTaskBarActivate(self, event) :
365        if self.IsIconized() :
[58]366            self.Iconize(False)
[59]367        if not self.IsShown() :
[58]368            self.Show(True)
369        self.Raise()
370
[64]371    def OnClose(self, event) :
[59]372        if hasattr(self, "chrono") :
373            self.chrono.Stop()
374            del self.chrono
[66]375        if hasattr(self, "quotasgrid") :
376            self.quotasgrid.Close()
377            self.quotasgrid.Destroy()
378            del self.quotasgrid
[58]379        if hasattr(self, "menu") :
[64]380            self.menu.Destroy()
[58]381            del self.menu
[59]382        if hasattr(self, "tbicon") :
[58]383            self.tbicon.Destroy()
384            del self.tbicon
385        self.Destroy()
386
[59]387    def OnTaskBarMenu(self, evt) :
[58]388        self.tbicon.PopupMenu(self.menu)
389
[59]390    def OnTaskBarClose(self, evt) :
[58]391        self.Close()
[72]392        #wxPython.wx.wxGetApp().ProcessIdle()
[58]393
[72]394class PyKotIconApp(wx.PySimpleApp):
[58]395    def OnInit(self) :
[63]396        try :
397            frame = PyKotIcon(None, -1)
398        except :   
399            crashed()
400        else :   
401            frame.Show(True)
[58]402        return True
403       
[63]404def main():
405    """Program's entry point."""
[65]406    try :
407        locale.setlocale(locale.LC_ALL, "")
408    except (locale.Error, IOError) :
409        sys.stderr.write("Problem while setting locale.\n")
410    try :
411        gettext.install("pykoticon")
412    except :
413        gettext.NullTranslations().install()
[63]414    app = PyKotIconApp()
415    app.MainLoop()
416   
[58]417def crashed() :   
418    """Minimal crash method."""
419    import traceback
420    lines = []
421    for line in traceback.format_exception(*sys.exc_info()) :
422        lines.extend([l for l in line.split("\n") if l])
423    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
424    sys.stderr.write(msg)
425    sys.stderr.flush()
[47]426   
[58]427def test() :
428    """Runs in test mode (console)."""
[60]429    if len(sys.argv) >= 3 :
430        username = sys.argv[2] 
431    else :   
[65]432        username = getCurrentUserName()
[58]433    net = CGINetworkInterface(DUMPYKOTA_URL)
[66]434    user = net.getUserInfo(username)
[65]435    print "UserName : ", user.UserName
[58]436    print "LimitBy : ", user.LimitBy
437    print "Balance : ", user.Balance
[66]438    for printername in user.Quotas.keys() :
439        quota = user.Quotas[printername]
440        print "\tPrinterName : ", printername
441        print "\tPageCounter : ", quota.PageCounter
442        print "\tSoftLimit : ", quota.SoftLimit
443        print "\tHardLimit : ", quota.HardLimit
444        print "\tDateLimit : ", quota.DateLimit
[58]445        print
[66]446    for printername in user.Printers.keys() :
447        printer = user.Printers[printername]
448        print "\tPrinterName : ", printername
449        print "\tPrice per Page : ", printer.PricePerPage
450        print "\tPrice per Job : ", printer.PricePerJob
451        print
[47]452
[58]453if __name__ == '__main__':
[60]454    if (len(sys.argv) >= 2) and (sys.argv[1] == "--test") :
[58]455        test()
456    else :   
457        main()
458   
Note: See TracBrowser for help on using the browser.