root / pykoticon / trunk / bin / pykoticon @ 86

Revision 86, 19.4 kB (checked in by jerome, 19 years ago)

Version number change

  • Property svn:keywords set to Id
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKotIcon - Windows System Tray Icon for PyKota
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#
21# $Id$
22#
23#
24
25import sys
26import os
27import urllib
28import urllib2
29import locale
30import gettext
31
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."
38    else :   
39        iconsdir = os.path.split(sys.argv[0])[0]
40else :       
41    isWindows = 0
42    iconsdir = "/usr/share/pykoticon"   # TODO : change this
43    import pwd
44   
45try :   
46    import wxPython.wx
47    import wx
48    import wx.grid as gridlib
49    hasWxPython = 1
50except ImportError :   
51    hasWxPython = 0
52    raise ImportError, "wxPython is missing. Please install it."
53   
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
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."""
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           
131class 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   
199class 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           
274class PyKotIcon(wxPython.wx.wxFrame):
275    """Main class."""
276    def __init__(self, parent, id):
277        wxPython.wx.wxFrame.__init__(self, parent, -1, \
278               _("PyKota Print Quota for user %s") % getCurrentUserName(), \
279               size = (460, -1), \
280               style = wxPython.wx.wxDEFAULT_FRAME_STYLE \
281                     | wxPython.wx.wxSIZE_AUTO_HEIGHT \
282                     | wxPython.wx.wxSIZE_AUTO_WIDTH \
283                     | wxPython.wx.wxICONIZE \
284                     | wxPython.wx.wxNO_FULL_REPAINT_ON_RESIZE)
285        try :             
286            self.tbicon = wxPython.wx.wxTaskBarIcon()
287        except AttributeError :   
288            self.tbicon = None # No taskbar icon facility, old wxWidgets maybe
289       
290        self.greenicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-green.ico"), \
291                                  wxPython.wx.wxBITMAP_TYPE_ICO)
292        self.redicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-red.ico"), \
293                                  wxPython.wx.wxBITMAP_TYPE_ICO)
294       
295        self.SetIcon(self.greenicon)
296        if self.tbicon is not None :
297            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
298            wxPython.wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
299            wxPython.wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
300       
301            self.TBMENU_RESTORE = wx.NewId()
302            self.TBMENU_CLOSE = wx.NewId()
303            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
304                                              self.OnTaskBarActivate)
305            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
306                                              self.OnTaskBarClose)
307            self.menu = wxPython.wx.wxMenu()
308            self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
309            self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
310       
311        wxPython.wx.EVT_ICONIZE(self, self.OnIconify)
312        wxPython.wx.EVT_CLOSE(self, self.OnClose)
313       
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
390   
391    def OnIconify(self, event) :
392        self.Hide()
393
394    def OnTaskBarActivate(self, event) :
395        if self.IsIconized() :
396            self.Iconize(False)
397        if not self.IsShown() :
398            self.Show(True)
399        self.Raise()
400
401    def OnClose(self, event) :
402        if hasattr(self, "chrono") :
403            self.chrono.Stop()
404            del self.chrono
405        if hasattr(self, "quotasgrid") :
406            self.quotasgrid.Close()
407            self.quotasgrid.Destroy()
408            del self.quotasgrid
409        if hasattr(self, "menu") :
410            self.menu.Destroy()
411            del self.menu
412        if hasattr(self, "tbicon") :
413            self.tbicon.Destroy()
414            del self.tbicon
415        self.Destroy()
416
417    def OnTaskBarMenu(self, evt) :
418        self.tbicon.PopupMenu(self.menu)
419
420    def OnTaskBarClose(self, evt) :
421        self.Close()
422
423class PyKotIconApp(wx.PySimpleApp):
424    def OnInit(self) :
425        try :
426            self.frame = PyKotIcon(None, -1)
427        except :   
428            crashed()
429        return True
430       
431    def postInit(self, configFile) :   
432        """Continues processing."""
433        self.frame.postInit(configFile)
434        self.frame.Show(True)
435       
436def main(conffile):
437    """Program's entry point."""
438    try :
439        locale.setlocale(locale.LC_ALL, "")
440    except (locale.Error, IOError) :
441        sys.stderr.write("Problem while setting locale.\n")
442    try :
443        gettext.install("pykoticon")
444    except :
445        gettext.NullTranslations().install()
446    app = PyKotIconApp()
447    app.postInit(conffile)
448    app.MainLoop()
449   
450def crashed() :   
451    """Minimal crash method."""
452    import traceback
453    lines = []
454    for line in traceback.format_exception(*sys.exc_info()) :
455        lines.extend([l for l in line.split("\n") if l])
456    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
457    sys.stderr.write(msg)
458    sys.stderr.flush()
459   
460# def test() :
461#     """Runs in test mode (console)."""
462#     print "Configuration file is ignored."
463#     if len(sys.argv) >= 3 :
464#         username = sys.argv[2]
465#     else :   
466#         username = getCurrentUserName()
467#     net = CGINetworkInterface(DUMPYKOTA_URL)
468#     user = net.getUserInfo(username)
469#     print "UserName : ", user.UserName
470#     print "LimitBy : ", user.LimitBy
471#     print "Balance : ", user.Balance
472#     for printername in user.Quotas.keys() :
473#         quota = user.Quotas[printername]
474#         print "\tPrinterName : ", printername
475#         print "\tPageCounter : ", quota.PageCounter
476#         print "\tSoftLimit : ", quota.SoftLimit
477#         print "\tHardLimit : ", quota.HardLimit
478#         print "\tDateLimit : ", quota.DateLimit
479#         print
480#     for printername in user.Printers.keys() :
481#         printer = user.Printers[printername]
482#         print "\tPrinterName : ", printername
483#         print "\tPrice per Page : ", printer.PricePerPage
484#         print "\tPrice per Job : ", printer.PricePerJob
485#         print
486
487if __name__ == '__main__':
488    if len(sys.argv) >= 2 :
489        arg = sys.argv[1]
490        if arg in ("-v", "--version") :   
491            print "0.2"
492        elif arg in ("-h", "--help") :   
493            print "usage : pykoticon configuration_file"
494        #elif arg == "--test" :
495        #    test()
496        else :
497            main(arg)
498    else :   
499        main("pykoticon.conf")
Note: See TracBrowser for help on using the browser.