]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/rpcMixin.py
removed second parameter from GetFirstChild calls
[wxWidgets.git] / wxPython / wx / lib / rpcMixin.py
1 #
2 # This was modified from rpcMixin.py distributed with wxPython
3 #
4 #----------------------------------------------------------------------
5 # Name: rpcMixin
6 # Version: 0.2.0
7 # Purpose: provides xmlrpc server functionality for wxPython
8 # applications via a mixin class
9 #
10 # Requires: (1) Python with threading enabled.
11 # (2) xmlrpclib from PythonWare
12 # (http://www.pythonware.com/products/xmlrpc/)
13 # the code was developed and tested using version 0.9.8
14 #
15 # Author: greg Landrum (Landrum@RationalDiscovery.com)
16 #
17 # Copyright: (c) 2000, 2001 by Greg Landrum and Rational Discovery LLC
18 # Licence: wxWindows license
19 #----------------------------------------------------------------------
20
21 """provides xmlrpc server functionality for wxPython applications via a mixin class
22
23 **Some Notes:**
24
25 1) The xmlrpc server runs in a separate thread from the main GUI
26 application, communication between the two threads using a custom
27 event (see the Threads demo in the wxPython docs for more info).
28
29 2) Neither the server nor the client are particularly smart about
30 checking method names. So it's easy to shoot yourself in the foot
31 by calling improper methods. It would be pretty easy to add
32 either a list of allowed methods or a list of forbidden methods.
33
34 3) Authentication of xmlrpc clients is *not* performed. I think it
35 would be pretty easy to do this in a hacky way, but I haven't done
36 it yet.
37
38 4) See the bottom of this file for an example of using the class.
39
40 **Obligatory disclaimer:**
41 This is my first crack at both using xmlrpc and multi-threaded
42 programming, so there could be huge horrible bugs or design
43 flaws. If you see one, I'd love to hear about them.
44
45 """
46
47
48 """ ChangeLog
49 23 May 2001: Version bumped to 0.2.0
50 Numerous code and design changes
51
52 21 Mar. 2001: Version bumped to 0.1.4
53 Updated rpcMixin.OnExternal to support methods with further references
54 (i.e. now you can do rpcClient.foo.bar() and have it work)
55 This probably ain't super legal in xmlrpc land, but it works just fine here
56 and we need it.
57
58 6 Mar. 2001: Version bumped to 0.1.3
59 Documentation changes to make this compatible with happydoc
60
61 21 Jan. 2001: Version bumped to 0.1.2
62 OnExternal() method in the mixin class now uses getattr() to check if
63 a desired method is present. It should have been done this way in
64 the first place.
65 14 Dec. 2000: Version bumped to 0.1.1
66 rearranged locking code and made other changes so that multiple
67 servers in one application are possible.
68
69 """
70
71 from wxPython.wx import *
72 import xmlrpcserver,xmlrpclib
73 import threading
74 import SocketServer
75 import new
76 import sys
77
78 rpcPENDING = 0
79 rpcDONE = 1
80 rpcEXCEPT = 2
81
82 class RPCRequest:
83 """A wrapper to use for handling requests and their responses"""
84 status = rpcPENDING
85 result = None
86
87 # here's the ID for external events
88 wxEVT_EXTERNAL_EVENT = 25015
89 class ExternalEvent(wxPyEvent):
90 """The custom event class used to pass xmlrpc calls from
91 the server thread into the GUI thread
92
93 """
94 def __init__(self,method,args):
95 wxPyEvent.__init__(self)
96 self.SetEventType(wxEVT_EXTERNAL_EVENT)
97 self.method = method
98 self.args = args
99 self.rpcStatus = RPCRequest()
100 self.rpcStatusLock = threading.Lock()
101 self.rpcCondVar = threading.Condition()
102
103 def Destroy(self):
104 self.method=None
105 self.args=None
106 self.rpcStatus = None
107 self.rpcStatusLock = None
108 self.rpcondVar = None
109
110 def EVT_EXTERNAL_EVENT(win,func):
111 win.Connect(-1,-1,wxEVT_EXTERNAL_EVENT,func)
112
113 class Handler(xmlrpcserver.RequestHandler):
114 """The handler class that the xmlrpcserver actually calls
115 when a request comes in.
116
117 """
118 def log_message(self,*args):
119 """ causes the server to stop spewing messages every time a request comes in
120
121 """
122 pass
123 def call(self,method,params):
124 """When an xmlrpc request comes in, this is the method that
125 gets called.
126
127 **Arguments**
128
129 - method: name of the method to be called
130
131 - params: arguments to that method
132
133 """
134 if method == '_rpcPing':
135 # we just acknowledge these without processing them
136 return 'ack'
137
138 # construct the event
139 evt = ExternalEvent(method,params)
140
141 # update the status variable
142 evt.rpcStatusLock.acquire()
143 evt.rpcStatus.status = rpcPENDING
144 evt.rpcStatusLock.release()
145
146 evt.rpcCondVar.acquire()
147 # dispatch the event to the GUI
148 wxPostEvent(self._app,evt)
149
150 # wait for the GUI to finish
151 while evt.rpcStatus.status == rpcPENDING:
152 evt.rpcCondVar.wait()
153 evt.rpcCondVar.release()
154 evt.rpcStatusLock.acquire()
155 if evt.rpcStatus.status == rpcEXCEPT:
156 # The GUI threw an exception, release the status lock
157 # and re-raise the exception
158 evt.rpcStatusLock.release()
159 raise evt.rpcStatus.result[0],evt.rpcStatus.result[1]
160 else:
161 # everything went through without problems
162 s = evt.rpcStatus.result
163
164 evt.rpcStatusLock.release()
165 evt.Destroy()
166 self._app = None
167 return s
168
169 # this global Event is used to let the server thread
170 # know when it should quit
171 stopEvent = threading.Event()
172 stopEvent.clear()
173
174 class _ServerThread(threading.Thread):
175 """ this is the Thread class which actually runs the server
176
177 """
178 def __init__(self,server,verbose=0):
179 self._xmlServ = server
180 threading.Thread.__init__(self,verbose=verbose)
181
182 def stop(self):
183 stopEvent.set()
184
185 def shouldStop(self):
186 return stopEvent.isSet()
187
188 def run(self):
189 while not self.shouldStop():
190 self._xmlServ.handle_request()
191 self._xmlServ = None
192
193 class rpcMixin:
194 """A mixin class to provide xmlrpc server functionality to wxPython
195 frames/windows
196
197 If you want to customize this, probably the best idea is to
198 override the OnExternal method, which is what's invoked when an
199 RPC is handled.
200
201 """
202
203 # we'll try a range of ports for the server, this is the size of the
204 # range to be scanned
205 nPortsToTry=20
206 if sys.platform == 'win32':
207 defPort = 800
208 else:
209 defPort = 8023
210
211 def __init__(self,host='',port=-1,verbose=0,portScan=1):
212 """Constructor
213
214 **Arguments**
215
216 - host: (optional) the hostname for the server
217
218 - port: (optional) the port the server will use
219
220 - verbose: (optional) if set, the server thread will be launched
221 in verbose mode
222
223 - portScan: (optional) if set, we'll scan across a number of ports
224 to find one which is avaiable
225
226 """
227 if port == -1:
228 port = self.defPort
229 self.verbose=verbose
230 EVT_EXTERNAL_EVENT(self,self.OnExternal)
231 if hasattr(self,'OnClose'):
232 self._origOnClose = self.OnClose
233 self.Disconnect(-1,-1,wxEVT_CLOSE_WINDOW)
234 else:
235 self._origOnClose = None
236 self.OnClose = self.RPCOnClose
237 EVT_CLOSE(self,self.RPCOnClose)
238
239 tClass = new.classobj('Handler%d'%(port),(Handler,),{})
240 tClass._app = self
241 if portScan:
242 self.rpcPort = -1
243 for i in xrange(self.nPortsToTry):
244 try:
245 xmlServ = SocketServer.TCPServer((host,port+i),tClass)
246 except:
247 pass
248 else:
249 self.rpcPort = port+i
250 else:
251 self.rpcPort = port
252 try:
253 xmlServ = SocketServer.TCPServer((host,port),tClass)
254 except:
255 self.rpcPort = -1
256
257 if self.rpcPort == -1:
258 raise 'RPCMixinError','Cannot initialize server'
259 self.servThread = _ServerThread(xmlServ,verbose=self.verbose)
260 self.servThread.setName('XML-RPC Server')
261 self.servThread.start()
262
263 def RPCOnClose(self,event):
264 """ callback for when the application is closed
265
266 be sure to shutdown the server and the server thread before
267 leaving
268
269 """
270 # by setting the global stopEvent we inform the server thread
271 # that it's time to shut down.
272 stopEvent.set()
273 if event is not None:
274 # if we came in here from a user event (as opposed to an RPC event),
275 # then we'll need to kick the server one last time in order
276 # to get that thread to terminate. do so now
277 s1 = xmlrpclib.Server('http://localhost:%d'%(self.rpcPort))
278 try:
279 s1._rpcPing()
280 except:
281 pass
282
283 if self._origOnClose is not None:
284 self._origOnClose(event)
285
286 def RPCQuit(self):
287 """ shuts down everything, including the rpc server
288
289 """
290 self.RPCOnClose(None)
291 def OnExternal(self,event):
292 """ this is the callback used to handle RPCs
293
294 **Arguments**
295
296 - event: an _ExternalEvent_ sent by the rpc server
297
298 Exceptions are caught and returned in the global _rpcStatus
299 structure. This allows the xmlrpc server to report the
300 exception to the client without mucking up any of the delicate
301 thread stuff.
302
303 """
304 event.rpcStatusLock.acquire()
305 doQuit = 0
306 try:
307 methsplit = event.method.split('.')
308 meth = self
309 for piece in methsplit:
310 meth = getattr(meth,piece)
311 except AttributeError,msg:
312 event.rpcStatus.result = 'No Such Method',msg
313 event.rpcStatus.status = rpcEXCEPT
314 else:
315 try:
316 res = apply(meth,event.args)
317 except:
318 import traceback
319 if self.verbose: traceback.print_exc()
320 event.rpcStatus.result = sys.exc_info()[:2]
321 event.rpcStatus.status = rpcEXCEPT
322 else:
323 if res is None:
324 # returning None across the xmlrpc interface is problematic
325 event.rpcStatus.result = []
326 else:
327 event.rpcStatus.result = res
328 event.rpcStatus.status = rpcDONE
329
330 event.rpcStatusLock.release()
331
332 # broadcast (using the condition var) that we're done with the event
333 event.rpcCondVar.acquire()
334 event.rpcCondVar.notify()
335 event.rpcCondVar.release()
336
337
338 if __name__ == '__main__':
339 import time
340 if sys.platform == 'win32':
341 port = 800
342 else:
343 port = 8023
344
345 class rpcFrame(wxFrame,rpcMixin):
346 """A simple wxFrame with the rpcMixin functionality added
347 """
348 def __init__(self,*args,**kwargs):
349 """ rpcHost or rpcPort keyword arguments will be passed along to
350 the xmlrpc server.
351 """
352 mixinArgs = {}
353 if kwargs.has_key('rpcHost'):
354 mixinArgs['host'] = kwargs['rpcHost']
355 del kwargs['rpcHost']
356 if kwargs.has_key('rpcPort'):
357 mixinArgs['port'] = kwargs['rpcPort']
358 del kwargs['rpcPort']
359 if kwargs.has_key('rpcPortScan'):
360 mixinArgs['portScan'] = kwargs['rpcPortScan']
361 del kwargs['rpcPortScan']
362
363 apply(wxFrame.__init__,(self,)+args,kwargs)
364 apply(rpcMixin.__init__,(self,),mixinArgs)
365
366 EVT_CHAR(self,self.OnChar)
367
368 def TestFunc(self,args):
369 """a demo method"""
370 return args
371
372 def OnChar(self,event):
373 key = event.GetKeyCode()
374 if key == ord('q'):
375 self.OnQuit(event)
376
377 def OnQuit(self,event):
378 self.OnClose(event)
379
380 def OnClose(self,event):
381 self.Destroy()
382
383
384
385 class MyApp(wxApp):
386 def OnInit(self):
387 self.frame = rpcFrame(NULL, -1, "wxPython RPCDemo", wxDefaultPosition,
388 wxSize(300,300),
389 rpcHost='localhost',rpcPort=port)
390 self.frame.Show(True)
391 return True
392
393
394 def testcon(port):
395 s1 = xmlrpclib.Server('http://localhost:%d'%(port))
396 s1.SetTitle('Munged')
397 s1._rpcPing()
398 if doQuit:
399 s1.RPCQuit()
400
401 doQuit = 1
402 if len(sys.argv)>1 and sys.argv[1] == '-q':
403 doQuit = 0
404 nT = threading.activeCount()
405 app = MyApp(0)
406 activePort = app.frame.rpcPort
407 t = threading.Thread(target=lambda x=activePort:testcon(x),verbose=0)
408 t.start()
409
410 app.MainLoop()
411 # give the threads time to shut down
412 if threading.activeCount() > nT:
413 print 'waiting for all threads to terminate'
414 while threading.activeCount() > nT:
415 time.sleep(0.5)
416
417