Changeset 87

Show
Ignore:
Timestamp:
01/25/06 00:24:36 (18 years ago)
Author:
jerome
Message:

Rewritten almost from scratch. Now includes an xml-rpc server

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • pykoticon/trunk/bin/pykoticon

    r86 r87  
    2929import locale 
    3030import gettext 
     31import socket 
     32import threading 
     33import SimpleXMLRPCServer 
     34 
     35import time 
    3136 
    3237if sys.platform == "win32" : 
     
    4651    import wxPython.wx 
    4752    import wx 
    48     import wx.grid as gridlib 
    4953    hasWxPython = 1 
    5054except ImportError :     
     
    6266            return "** Unknown **" 
    6367         
    64 class 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  
    78 class 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          
    97 class 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              
    131 class 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      
    199 class 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              
    274 class PyKotIcon(wxPython.wx.wxFrame): 
     68class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) : 
     69    """My own server class.""" 
     70    def __init__(self, frame, printserver, localport) : 
     71        myIPAddress = socket.gethostbyaddr(socket.gethostname())[2][0] 
     72        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, (myIPAddress, localport)) 
     73        self.printServer = printserver 
     74        self.frame = frame 
     75        loop = threading.Thread(target=self.mainloop) 
     76        loop.start() 
     77         
     78    def export_quitApplication(self) :     
     79        """Makes the application quit.""" 
     80        self.frame.quitEvent.set() 
     81        return True 
     82         
     83    def export_openDialog(self) :     
     84        """Opens a dialog to ask username, password, etc...""" 
     85        print "Open dialog !" 
     86        return ("jerome", "blah") 
     87         
     88    def verify_request(self, request, client_address) : 
     89        """Ensures that requests which don't come from the print server are rejected.""" 
     90        (client, port) = client_address 
     91        if socket.gethostbyname(self.printServer) == client : 
     92            return True 
     93        else : 
     94            # Unauthorized access ! 
     95            return False 
     96         
     97    def _dispatch(self, method, params) :     
     98        """Ensure that only export_* methods are available.""" 
     99        return getattr(self, "export_%s" % method)(*params) 
     100         
     101    def mainloop(self) : 
     102        """XML-RPC Server's main loop.""" 
     103        self.register_function(self.export_openDialog) 
     104        self.register_function(self.export_quitApplication) 
     105        while not self.frame.quitEvent.isSet() : 
     106            self.handle_request() 
     107        self.frame.Close()   
     108        sys.exit(0) 
     109     
     110class PyKotIcon(wx.Frame): 
    275111    """Main class.""" 
    276112    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), \ 
     113        wx.Frame.__init__(self, parent, -1, \ 
     114               _("PyKota for user %s") % getCurrentUserName(), \ 
     115               size = (-1, -1), \ 
    280116               style = wxPython.wx.wxDEFAULT_FRAME_STYLE \ 
    281117                     | wxPython.wx.wxSIZE_AUTO_HEIGHT \ 
     
    312148        wxPython.wx.EVT_CLOSE(self, self.OnClose) 
    313149         
    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 
     150    def postInit(self, printserver, localport) :     
     151        """Starts the XML-RPC server.""" 
     152        self.quitEvent = threading.Event() 
     153        self.server = MyXMLRPCServer(self, printserver, localport) 
    390154     
    391155    def OnIconify(self, event) : 
     
    400164 
    401165    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 
     166        sys.stderr.write("Close event !\n") 
     167        if not self.quitEvent.isSet() : 
     168            self.quitEvent.set() 
    409169        if hasattr(self, "menu") : 
    410170            self.menu.Destroy() 
    411171            del self.menu 
    412         if hasattr(self, "tbicon") : 
     172        if hasattr(self, "tbicon") and self.tbicon : 
    413173            self.tbicon.Destroy() 
    414174            del self.tbicon 
     
    416176 
    417177    def OnTaskBarMenu(self, evt) : 
    418         self.tbicon.PopupMenu(self.menu) 
     178        if self.tbicon : 
     179            self.tbicon.PopupMenu(self.menu) 
    419180 
    420181    def OnTaskBarClose(self, evt) : 
     
    429190        return True 
    430191         
    431     def postInit(self, configFile) :     
     192    def postInit(self, printserver, localport) :     
    432193        """Continues processing.""" 
    433         self.frame.postInit(configFile) 
     194        self.frame.postInit(printserver, localport) 
    434195        self.frame.Show(True) 
    435196         
    436 def main(conffile): 
     197def main(printserver, localport): 
    437198    """Program's entry point.""" 
    438199    try : 
     
    445206        gettext.NullTranslations().install() 
    446207    app = PyKotIconApp() 
    447     app.postInit(conffile) 
     208    try : 
     209        localport = int(localport)     
     210    except (TypeError, ValueError) :     
     211        raise ValueError, "Invalid TCP port parameter %s\n" % localport 
     212    app.postInit(printserver, localport) 
    448213    app.MainLoop() 
    449214     
     
    458223    sys.stderr.flush() 
    459224     
    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  
    487225if __name__ == '__main__': 
    488226    if len(sys.argv) >= 2 : 
    489227        arg = sys.argv[1] 
    490228        if arg in ("-v", "--version") :     
    491             print "0.2" 
     229            print "0.3" 
    492230        elif arg in ("-h", "--help") :     
    493             print "usage : pykoticon configuration_file" 
    494         #elif arg == "--test" : 
    495         #    test() 
     231            print "usage : pykoticon printserver_hostname localport" 
    496232        else : 
    497             main(arg) 
     233            main(*sys.argv[1:3]) 
    498234    else :     
    499         main("pykoticon.conf") 
     235        main("localhost", "7654")