1__all__ = ['AsyncPythonInterpreter'] 2 3try: 4 import fcntl 5except: 6 fcntl = None 7import os 8import sys 9import socket 10from StringIO import StringIO 11from netrepr import NetRepr, RemoteObjectPool, RemoteObjectReference 12import objc 13from Foundation import * 14 15IMPORT_MODULES = ['netrepr', 'remote_console', 'remote_pipe', 'remote_bootstrap'] 16source = StringIO() 17for fn in IMPORT_MODULES: 18 for line in file(fn+'.py', 'rU'): 19 source.write(line) 20 source.write('\n\n') 21SOURCE = repr(source.getvalue()) + '\n' 22 23def bind_and_listen(hostport): 24 if isinstance(hostport, str): 25 host, port = hostport.split(':') 26 hostport = (host, int(port)) 27 serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 # set close-on-exec 29 if hasattr(fcntl, 'FD_CLOEXEC'): 30 old = fcntl.fcntl(serversock.fileno(), fcntl.F_GETFD) 31 fcntl.fcntl(serversock.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC) 32 # allow the address to be re-used in a reasonable amount of time 33 if os.name == 'posix' and sys.platform != 'cygwin': 34 serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 35 36 serversock.bind(hostport) 37 serversock.listen(5) 38 return serversock 39 40class AsyncPythonInterpreter(NSObject): 41 42 commandReactor = objc.IBOutlet('commandReactor') 43 44 def init(self): 45 self = super(AsyncPythonInterpreter, self).init() 46 self.host = None 47 self.port = None 48 self.interpreterPath = None 49 self.scriptPath = None 50 self.commandReactor = None 51 self.serverSocket = None 52 self.serverFileHandle = None 53 self.buffer = '' 54 self.serverFileHandle = None 55 self.remoteFileHandle = None 56 self.childTask = None 57 return self 58 59 def initWithHost_port_interpreterPath_scriptPath_commandReactor_(self, host, port, interpreterPath, scriptPath, commandReactor): 60 self = self.init() 61 self.host = host 62 self.port = port 63 self.interpreterPath = interpreterPath 64 self.scriptPath = scriptPath 65 self.commandReactor = commandReactor 66 self.serverSocket = None 67 return self 68 69 def awakeFromNib(self): 70 defaults = NSUserDefaults.standardUserDefaults() 71 def default(k, v, typeCheck=None): 72 rval = defaults.objectForKey_(k) 73 if typeCheck is not None and rval is not None: 74 try: 75 rval = typeCheck(rval) 76 except TypeError: 77 NSLog(u'%s failed type check %s with value %r' % (k, typeCheck.__name__, rval)) 78 rval = None 79 if rval is None: 80 defaults.setObject_forKey_(v, k) 81 rval = v 82 return rval 83 self.host = default(u'AsyncPythonInterpreterInterpreterHost', u'127.0.0.1', str) 84 self.port = default(u'AsyncPythonInterpreterInterpreterPort', 0, int) 85 self.interpreterPath = default(u'AsyncPythonInterpreterInterpreterPath', u'/usr/bin/python', unicode) 86 self.scriptPath = type(self).bundleForClass().pathForResource_ofType_(u'tcpinterpreter', u'py') 87 88 def connect(self): 89 #NSLog(u'connect') 90 self.serverSocket = bind_and_listen((self.host, self.port)) 91 self.serverFileHandle = NSFileHandle.alloc().initWithFileDescriptor_(self.serverSocket.fileno()) 92 nc = NSNotificationCenter.defaultCenter() 93 nc.addObserver_selector_name_object_( 94 self, 95 'remoteSocketAccepted:', 96 NSFileHandleConnectionAcceptedNotification, 97 self.serverFileHandle) 98 self.serverFileHandle.acceptConnectionInBackgroundAndNotify() 99 self.remoteFileHandle = None 100 for k in os.environ.keys(): 101 if k.startswith('PYTHON'): 102 del os.environ[k] 103 self.childTask = NSTask.launchedTaskWithLaunchPath_arguments_(self.interpreterPath, [self.scriptPath, repr(self.serverSocket.getsockname())]) 104 nc.addObserver_selector_name_object_( 105 self, 106 'childTaskTerminated:', 107 NSTaskDidTerminateNotification, 108 self.childTask) 109 return self 110 111 def remoteSocketAccepted_(self, notification): 112 #NSLog(u'remoteSocketAccepted_') 113 self.serverFileHandle.closeFile() 114 self.serverFileHandle = None 115 ui = notification.userInfo() 116 self.remoteFileHandle = ui.objectForKey_(NSFileHandleNotificationFileHandleItem) 117 nc = NSNotificationCenter.defaultCenter() 118 nc.addObserver_selector_name_object_( 119 self, 120 'remoteFileHandleReadCompleted:', 121 NSFileHandleReadCompletionNotification, 122 self.remoteFileHandle) 123 self.writeBytes_(SOURCE) 124 self.remoteFileHandle.readInBackgroundAndNotify() 125 self.commandReactor.connectionEstablished_(self) 126 NSNotificationCenter.defaultCenter().postNotificationName_object_(u'AsyncPythonInterpreterOpened', self) 127 128 def remoteFileHandleReadCompleted_(self, notification): 129 #NSLog(u'remoteFileHandleReadCompleted_') 130 ui = notification.userInfo() 131 newData = ui.objectForKey_(NSFileHandleNotificationDataItem) 132 if newData is None: 133 self.close() 134 NSLog(u'Error: %r' % (ui.objectForKey_(NSFileHandleError),)) 135 return 136 bytes = newData.bytes()[:] 137 if len(bytes) == 0: 138 self.close() 139 return 140 self.remoteFileHandle.readInBackgroundAndNotify() 141 start = len(self.buffer) 142 buff = self.buffer + newData.bytes()[:] 143 #NSLog(u'current buffer: %r' % (buff,)) 144 lines = [] 145 while True: 146 linebreak = buff.find('\n', start) + 1 147 if linebreak == 0: 148 break 149 lines.append(buff[:linebreak]) 150 buff = buff[linebreak:] 151 start = 0 152 #NSLog(u'lines: %r' % (lines,)) 153 self.buffer = buff 154 for line in lines: 155 self.commandReactor.lineReceived_fromConnection_(line, self) 156 157 def writeBytes_(self, bytes): 158 #NSLog(u'Writing bytes: %r' % (bytes,)) 159 try: 160 self.remoteFileHandle.writeData_(NSData.dataWithBytes_length_(bytes, len(bytes))) 161 except objc.error: 162 self.close() 163 #NSLog(u'bytes written.') 164 165 def childTaskTerminated_(self, notification): 166 #NSLog(u'childTaskTerminated_') 167 self.close() 168 169 def closeServerFileHandle(self): 170 #NSLog(u'closeServerFileHandle') 171 if self.serverFileHandle is not None: 172 try: 173 self.serverFileHandle.closeFile() 174 except objc.error: 175 pass 176 self.serverFileHandle = None 177 178 def closeRemoteFileHandle(self): 179 #NSLog(u'closeRemoteFileHandle') 180 if self.remoteFileHandle is not None: 181 try: 182 self.remoteFileHandle.closeFile() 183 except objc.error: 184 pass 185 self.remoteFileHandle = None 186 187 def terminateChildTask(self): 188 #NSLog(u'terminateChildTask') 189 if self.childTask is not None: 190 try: 191 self.childTask.terminate() 192 except objc.error: 193 pass 194 self.childTask = None 195 196 def close(self): 197 #NSLog(u'close') 198 NSNotificationCenter.defaultCenter().removeObserver_(self) 199 self.finalClose() 200 NSNotificationCenter.defaultCenter().postNotificationName_object_(u'AsyncPythonInterpreterClosed', self) 201 202 def finalClose(self): 203 if self.commandReactor is not None: 204 self.commandReactor.connectionClosed_(self) 205 self.commandReactor = None 206 self.closeServerFileHandle() 207 self.closeRemoteFileHandle() 208 self.terminateChildTask() 209 210def test_console(): 211 from PyObjCTools import AppHelper 212 from ConsoleReactor import ConsoleReactor 213 host = '127.0.0.1' 214 port = 0 215 interpreterPath = sys.executable 216 scriptPath = unicode(os.path.abspath('tcpinterpreter.py')) 217 commandReactor = ConsoleReactor.alloc().init() 218 interp = AsyncPythonInterpreter.alloc().initWithHost_port_interpreterPath_scriptPath_commandReactor_(host, port, interpreterPath, scriptPath, commandReactor) 219 interp.connect() 220 class ThisEventLoopStopper(NSObject): 221 def interpFinished_(self, notification): 222 AppHelper.stopEventLoop() 223 stopper = ThisEventLoopStopper.alloc().init() 224 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(stopper, 'interpFinished:', u'AsyncPythonInterpreterClosed', interp) 225 AppHelper.runConsoleEventLoop(installInterrupt=True) 226 227def main(): 228 test_console() 229 230if __name__ == '__main__': 231 main() 232