root / pykoticon / trunk / bin / pykoticon @ 84

Revision 84, 19.3 kB (checked in by jerome, 19 years ago)

Now uses semicolon separated values instead of comma separated
values to not break when dealing with datas coming from an
LDAP server

  • 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 = "."
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.1"
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.