__all__ = ['AsyncPythonInterpreter']
try:
import fcntl
except:
fcntl = None
import os
import sys
import socket
from StringIO import StringIO
from netrepr import NetRepr,RemoteObjectPool,RemoteObjectReference
import objc
from Foundation import *
IMPORT_MODULES = ['netrepr', 'remote_console', 'remote_pipe', 'remote_bootstrap']
source = StringIO()
for fn in IMPORT_MODULES:
for line in file(fn+'.py', 'rU'):
source.write(line)
source.write('\n\n')
SOURCE = repr(source.getvalue()) + '\n'
def bind_and_listen(hostport):
if isinstance(hostport, str):
host, port = hostport.split(':')
hostport = (host, int(port))
serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# set close-on-exec
if hasattr(fcntl, 'FD_CLOEXEC'):
old = fcntl.fcntl(serversock.fileno(), fcntl.F_GETFD)
fcntl.fcntl(serversock.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
# allow the address to be re-used in a reasonable amount of time
if os.name == 'posix' and sys.platform != 'cygwin':
serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversock.bind(hostport)
serversock.listen(5)
return serversock
class AsyncPythonInterpreter(NSObject):
commandReactor = objc.IBOutlet('commandReactor')
def init(self):
self = super(AsyncPythonInterpreter, self).init()
self.host = None
self.port = None
self.interpreterPath = None
self.scriptPath = None
self.commandReactor = None
self.serverSocket = None
self.serverFileHandle = None
self.buffer = ''
self.serverFileHandle = None
self.remoteFileHandle = None
self.childTask = None
return self
def initWithHost_port_interpreterPath_scriptPath_commandReactor_(self, host, port, interpreterPath, scriptPath, commandReactor):
self = self.init()
self.host = host
self.port = port
self.interpreterPath = interpreterPath
self.scriptPath = scriptPath
self.commandReactor = commandReactor
self.serverSocket = None
return self
def awakeFromNib(self):
defaults = NSUserDefaults.standardUserDefaults()
def default(k, v, typeCheck=None):
rval = defaults.objectForKey_(k)
if typeCheck is not None and rval is not None:
try:
rval = typeCheck(rval)
except TypeError:
NSLog(u'%s failed type check %s with value %r' % (k, typeCheck.__name__, rval))
rval = None
if rval is None:
defaults.setObject_forKey_(v, k)
rval = v
return rval
self.host = default(u'AsyncPythonInterpreterInterpreterHost', u'127.0.0.1', str)
self.port = default(u'AsyncPythonInterpreterInterpreterPort', 0, int)
self.interpreterPath = default(u'AsyncPythonInterpreterInterpreterPath', u'/usr/bin/python', unicode)
self.scriptPath = type(self).bundleForClass().pathForResource_ofType_(u'tcpinterpreter', u'py')
def connect(self):
#NSLog(u'connect')
self.serverSocket = bind_and_listen((self.host, self.port))
self.serverFileHandle = NSFileHandle.alloc().initWithFileDescriptor_(self.serverSocket.fileno())
nc = NSNotificationCenter.defaultCenter()
nc.addObserver_selector_name_object_(
self,
'remoteSocketAccepted:',
NSFileHandleConnectionAcceptedNotification,
self.serverFileHandle)
self.serverFileHandle.acceptConnectionInBackgroundAndNotify()
self.remoteFileHandle = None
for k in os.environ.keys():
if k.startswith('PYTHON'):
del os.environ[k]
self.childTask = NSTask.launchedTaskWithLaunchPath_arguments_(self.interpreterPath, [self.scriptPath, repr(self.serverSocket.getsockname())])
nc.addObserver_selector_name_object_(
self,
'childTaskTerminated:',
NSTaskDidTerminateNotification,
self.childTask)
return self
def remoteSocketAccepted_(self, notification):
#NSLog(u'remoteSocketAccepted_')
self.serverFileHandle.closeFile()
self.serverFileHandle = None
ui = notification.userInfo()
self.remoteFileHandle = ui.objectForKey_(NSFileHandleNotificationFileHandleItem)
nc = NSNotificationCenter.defaultCenter()
nc.addObserver_selector_name_object_(
self,
'remoteFileHandleReadCompleted:',
NSFileHandleReadCompletionNotification,
self.remoteFileHandle)
self.writeBytes_(SOURCE)
self.remoteFileHandle.readInBackgroundAndNotify()
self.commandReactor.connectionEstablished_(self)
NSNotificationCenter.defaultCenter().postNotificationName_object_(u'AsyncPythonInterpreterOpened', self)
def remoteFileHandleReadCompleted_(self, notification):
#NSLog(u'remoteFileHandleReadCompleted_')
ui = notification.userInfo()
newData = ui.objectForKey_(NSFileHandleNotificationDataItem)
if newData is None:
self.close()
NSLog(u'Error: %r' % (ui.objectForKey_(NSFileHandleError),))
return
bytes = newData.bytes()[:]
if len(bytes) == 0:
self.close()
return
self.remoteFileHandle.readInBackgroundAndNotify()
start = len(self.buffer)
buff = self.buffer + newData.bytes()[:]
#NSLog(u'current buffer: %r' % (buff,))
lines = []
while True:
linebreak = buff.find('\n', start) + 1
if linebreak == 0:
break
lines.append(buff[:linebreak])
buff = buff[linebreak:]
start = 0
#NSLog(u'lines: %r' % (lines,))
self.buffer = buff
for line in lines:
self.commandReactor.lineReceived_fromConnection_(line, self)
def writeBytes_(self, bytes):
#NSLog(u'Writing bytes: %r' % (bytes,))
try:
self.remoteFileHandle.writeData_(NSData.dataWithBytes_length_(bytes, len(bytes)))
except objc.error:
self.close()
#NSLog(u'bytes written.')
def childTaskTerminated_(self, notification):
#NSLog(u'childTaskTerminated_')
self.close()
def closeServerFileHandle(self):
#NSLog(u'closeServerFileHandle')
if self.serverFileHandle is not None:
try:
self.serverFileHandle.closeFile()
except objc.error:
pass
self.serverFileHandle = None
def closeRemoteFileHandle(self):
#NSLog(u'closeRemoteFileHandle')
if self.remoteFileHandle is not None:
try:
self.remoteFileHandle.closeFile()
except objc.error:
pass
self.remoteFileHandle = None
def terminateChildTask(self):
#NSLog(u'terminateChildTask')
if self.childTask is not None:
try:
self.childTask.terminate()
except objc.error:
pass
self.childTask = None
def close(self):
#NSLog(u'close')
NSNotificationCenter.defaultCenter().removeObserver_(self)
self.finalClose()
NSNotificationCenter.defaultCenter().postNotificationName_object_(u'AsyncPythonInterpreterClosed', self)
def finalClose(self):
if self.commandReactor is not None:
self.commandReactor.connectionClosed_(self)
self.commandReactor = None
self.closeServerFileHandle()
self.closeRemoteFileHandle()
self.terminateChildTask()
def test_console():
from PyObjCTools import AppHelper
from ConsoleReactor import ConsoleReactor
host = '127.0.0.1'
port = 0
interpreterPath = sys.executable
scriptPath = unicode(os.path.abspath('tcpinterpreter.py'))
commandReactor = ConsoleReactor.alloc().init()
interp = AsyncPythonInterpreter.alloc().initWithHost_port_interpreterPath_scriptPath_commandReactor_(host, port, interpreterPath, scriptPath, commandReactor)
interp.connect()
class ThisEventLoopStopper(NSObject):
def interpFinished_(self, notification):
AppHelper.stopEventLoop()
stopper = ThisEventLoopStopper.alloc().init()
NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(stopper, 'interpFinished:', u'AsyncPythonInterpreterClosed', interp)
AppHelper.runConsoleEventLoop(installInterrupt=True)
def main():
test_console()
if __name__ == '__main__':
main()
|