root / pykoticon / trunk / bin / pykoticon @ 92

Revision 92, 10.1 kB (checked in by jerome, 18 years ago)

Now exit is really clean

  • Property svn:keywords set to Id
RevLine 
[47]1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
[89]4# PyKotIcon - Client side helper for PyKota
[47]5#
[89]6# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
[47]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
[87]31import socket
32import threading
[88]33import xmlrpclib
[87]34import SimpleXMLRPCServer
[47]35
[87]36import time
37
[65]38if sys.platform == "win32" :
39    isWindows = 1
40    try :
41        import win32api
42    except ImportError :   
43        raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them."
[76]44    else :   
[85]45        iconsdir = os.path.split(sys.argv[0])[0]
[65]46else :       
47    isWindows = 0
[76]48    iconsdir = "/usr/share/pykoticon"   # TODO : change this
[65]49    import pwd
[57]50   
[58]51try :   
52    import wxPython.wx
53    import wx
54    hasWxPython = 1
55except ImportError :   
56    hasWxPython = 0
[75]57    raise ImportError, "wxPython is missing. Please install it."
[58]58   
[65]59def getCurrentUserName() :
60    """Retrieves the current user's name."""
61    if isWindows :
62        return win32api.GetUserName()
63    else :   
64        try :
65            return pwd.getpwuid(os.geteuid())[0]
66        except :
67            return "** Unknown **"
68       
[87]69class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) :
70    """My own server class."""
[88]71    allow_reuse_address = True
72    def __init__(self, frame, printserver, localport, debug=False) :
73        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, ('0.0.0.0', localport))
[87]74        self.printServer = printserver
75        self.frame = frame
[88]76        self.debug = debug
[87]77        loop = threading.Thread(target=self.mainloop)
78        loop.start()
[56]79       
[88]80    def logDebug(self, message) :   
81        """Logs a debug message if debug mode is active."""
82        if self.debug :
83            sys.stderr.write("%s\n" % message)
84           
[87]85    def export_quitApplication(self) :   
86        """Makes the application quit."""
[88]87        self.logDebug("Remote host asked to close the application.")
[87]88        self.frame.quitEvent.set()
[92]89        wx.CallAfter(self.frame.OnClose, None)
[87]90        return True
[55]91       
[91]92    def export_openConfirmDialog(self, printername, username, jobid, jobtitle, jobsize) :   
93        """Opens a dialog to ask the user to confirm or cancel the print job.
[55]94       
[91]95           Returns True to confirm, False to cancel.
96        """   
97        wx.CallAfter(self.frame.askConfirmation, printername, username, jobid, jobtitle, jobsize)
[92]98       
99        # ugly, isn't it ?
[91]100        while self.frame.askConfirmationResult is None :
101            time.sleep(0.5)
102        retcode = self.frame.askConfirmationResult   
[92]103        self.frame.askConfirmationResult = None # prepare for next call, just in case
[91]104        return retcode
105       
[88]106    def export_nop(self) :   
107        """Does nothing, but allows a clean shutdown from the frame itself."""
108        self.logDebug("No operation !")
109        return True
110       
[87]111    def verify_request(self, request, client_address) :
112        """Ensures that requests which don't come from the print server are rejected."""
113        (client, port) = client_address
114        if socket.gethostbyname(self.printServer) == client :
[88]115            self.logDebug("%s accepted." % client)
[87]116            return True
117        else :
118            # Unauthorized access !
[88]119            self.logDebug("%s rejected." % client)
[87]120            return False
[55]121       
[87]122    def _dispatch(self, method, params) :   
123        """Ensure that only export_* methods are available."""
124        return getattr(self, "export_%s" % method)(*params)
[55]125       
[87]126    def mainloop(self) :
127        """XML-RPC Server's main loop."""
[91]128        self.register_function(self.export_openConfirmDialog)
[87]129        self.register_function(self.export_quitApplication)
[88]130        self.register_function(self.export_nop)
[87]131        while not self.frame.quitEvent.isSet() :
132            self.handle_request()
[88]133        self.server_close()   
[87]134        sys.exit(0)
[47]135   
[87]136class PyKotIcon(wx.Frame):
[63]137    """Main class."""
138    def __init__(self, parent, id):
[91]139        self.askConfirmationResult = None
140        wx.Frame.__init__(self, parent, wx.ID_ANY, \
[92]141               _("PyKota info for  %s") % getCurrentUserName(), \
[87]142               size = (-1, -1), \
[66]143               style = wxPython.wx.wxDEFAULT_FRAME_STYLE \
[72]144                     | wxPython.wx.wxSIZE_AUTO_HEIGHT \
145                     | wxPython.wx.wxSIZE_AUTO_WIDTH \
[66]146                     | wxPython.wx.wxNO_FULL_REPAINT_ON_RESIZE)
[91]147#        try :             
148#            self.tbicon = wxPython.wx.wxTaskBarIcon()
149#        except AttributeError :   
150#            self.tbicon = None # No taskbar icon facility, old wxWidgets maybe
151#       
152#        self.greenicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-green.ico"), \
153#                                  wxPython.wx.wxBITMAP_TYPE_ICO)
154#        self.redicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-red.ico"), \
155#                                  wxPython.wx.wxBITMAP_TYPE_ICO)
156#       
157#        self.SetIcon(self.greenicon)
158#        if self.tbicon is not None :
159#            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
160#            wxPython.wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
161#            wxPython.wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
162#       
163#            self.TBMENU_RESTORE = wx.NewId()
164#            self.TBMENU_CLOSE = wx.NewId()
165#            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
166#                                              self.OnTaskBarActivate)
167#            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
168#                                              self.OnTaskBarClose)
169#            self.menu = wxPython.wx.wxMenu()
170#            self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
171#            self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
172#       
[58]173        wxPython.wx.EVT_ICONIZE(self, self.OnIconify)
[64]174        wxPython.wx.EVT_CLOSE(self, self.OnClose)
[91]175        self.Show(True)
[64]176       
[88]177    def closeServer(self) :   
178        """Tells the xml-rpc server to exit."""
179        if not self.quitEvent.isSet() :
180            self.quitEvent.set()
181        server = xmlrpclib.ServerProxy("http://localhost:%s" % self.port)   
182        try :
[92]183            # wake the server with an empty request
184            # for it to see the event object
185            # which has just been set
[88]186            server.nop()
187        except :   
188            # Probably already stopped
189            pass
190       
[87]191    def postInit(self, printserver, localport) :   
192        """Starts the XML-RPC server."""
193        self.quitEvent = threading.Event()
[88]194        self.port = localport
195        self.server = MyXMLRPCServer(self, printserver, localport, debug=True)
[59]196   
197    def OnIconify(self, event) :
[72]198        self.Hide()
[58]199
[59]200    def OnTaskBarActivate(self, event) :
[91]201        #if self.IsIconized() :
202        #    self.Iconize(False)
[59]203        if not self.IsShown() :
[58]204            self.Show(True)
205        self.Raise()
206
[64]207    def OnClose(self, event) :
[87]208        sys.stderr.write("Close event !\n")
[88]209        self.closeServer()
[58]210        if hasattr(self, "menu") :
[64]211            self.menu.Destroy()
[58]212            del self.menu
[87]213        if hasattr(self, "tbicon") and self.tbicon :
[58]214            self.tbicon.Destroy()
215            del self.tbicon
216        self.Destroy()
217
[91]218    def OnTaskBarMenu(self, event) :
219        #if self.tbicon :
220        #    self.tbicon.PopupMenu(self.menu)
221        pass
[58]222
[91]223    def OnTaskBarClose(self, event) :
[58]224        self.Close()
[91]225       
226    def askConfirmation(self, printername, username, jobid, jobtitle, jobsize) :
227        """Asks for confirmation before printing."""
228        message = _("""Hello %(username)s,
229       
[92]230You sent job %(jobid)s (%(jobtitle)s) to printer %(printername)s.
231
232This job seems to be %(jobsize)s pages long.
233
234Please confirm or cancel.""") % locals()
[91]235                     
236        dialog = wx.MessageDialog(self, message, _("Confirmation"), wx.OK | wx.CANCEL)
237        self.askConfirmationResult = dialog.ShowModal()
238        dialog.Destroy()
[58]239
[72]240class PyKotIconApp(wx.PySimpleApp):
[58]241    def OnInit(self) :
[91]242        self.frame = PyKotIcon(None, -1)
[58]243        return True
244       
[87]245    def postInit(self, printserver, localport) :   
[81]246        """Continues processing."""
[87]247        self.frame.postInit(printserver, localport)
[81]248        self.frame.Show(True)
249       
[87]250def main(printserver, localport):
[63]251    """Program's entry point."""
[65]252    try :
253        locale.setlocale(locale.LC_ALL, "")
254    except (locale.Error, IOError) :
255        sys.stderr.write("Problem while setting locale.\n")
256    try :
257        gettext.install("pykoticon")
258    except :
259        gettext.NullTranslations().install()
[63]260    app = PyKotIconApp()
[87]261    try :
262        localport = int(localport)   
263    except (TypeError, ValueError) :   
264        raise ValueError, "Invalid TCP port parameter %s\n" % localport
265    app.postInit(printserver, localport)
[63]266    app.MainLoop()
267   
[58]268def crashed() :   
269    """Minimal crash method."""
270    import traceback
271    lines = []
272    for line in traceback.format_exception(*sys.exc_info()) :
273        lines.extend([l for l in line.split("\n") if l])
274    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
275    sys.stderr.write(msg)
276    sys.stderr.flush()
[47]277   
[58]278if __name__ == '__main__':
[81]279    if len(sys.argv) >= 2 :
280        arg = sys.argv[1]
281        if arg in ("-v", "--version") :   
[87]282            print "0.3"
[81]283        elif arg in ("-h", "--help") :   
[87]284            print "usage : pykoticon printserver_hostname localport"
[81]285        else :
[87]286            main(*sys.argv[1:3])
[58]287    else :   
[87]288        main("localhost", "7654")
Note: See TracBrowser for help on using the browser.