Skip to content

Instantly share code, notes, and snippets.

@AlexYukikaze
Last active November 22, 2016 09:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save AlexYukikaze/ee8cf9fdf337f3b77565 to your computer and use it in GitHub Desktop.
Save AlexYukikaze/ee8cf9fdf337f3b77565 to your computer and use it in GitHub Desktop.
World of Tanks remote REPL console for interactive debugging
from mods.remote import create_terminal
from gui import SystemMessages
def add_info(text):
SystemMessages.pushMessage(text, type=SystemMessages.SM_TYPE.Information)
def add_warning(text):
SystemMessages.pushMessage(text, type=SystemMessages.SM_TYPE.Warning)
HOST, PORT = "localhost", 9999
BANNER = """### WELCOME TO REMOTE CONSOLE ###"""
LOCALS = dict(author='Alex Yukikaze', info=add_info, warn=add_warning)
# Create the server, binding to localhost on port 9999
remote_terminal= create_terminal(HOST, PORT, banner=BANNER, local=LOCALS)
remote_terminal.update_locals(host=HOST, port=PORT)
# remote_terminal.exit() # for stop listening and unbind socket
'''
Simple remote REPL
$ nc 127.0.0.1 9999
@author: Alex Yukikaze (Putin Alexander)
@year: 2014
'''
import SocketServer
import contextlib
import threading
import code
@contextlib.contextmanager
def std_redirector(stdin, stdout, stderr):
import sys
orig_fds = sys.stdin, sys.stdout, sys.stderr
sys.stdin, sys.stdout, sys.stderr = stdin, stdout, stderr
yield
sys.stdin, sys.stdout, sys.stderr = orig_fds
class Event(object):
def __init__(self):
self.__handlers = []
def __iadd__(self, handler):
self.__handlers.append(handler)
return self
def __isub__(self, handler):
self.__handlers.remove(handler)
return self
def invoke(self, *args, **kwargs):
for handler in self.__handlers:
handler(*args, **kwargs)
def clear(self, inObject):
for handler in self.__handlers:
if handler.im_self == inObject:
self -= handler
class Interpreter:
def __init__(self, local={}):
self.console = code.InteractiveConsole(local)
self.console.locals['exit'] = self.exit
def interact(self, banner=''):
print banner
self.__running = True
more = False
try:
while self.__running:
line = raw_input('>>> ' if not more else '... ')
more = self.console.push(line)
except:
pass
def updateLocals(self, new_locals):
self.console.locals = new_locals
def exit(self):
self.__running = False
class __TerminalServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def __init__(self, addr, handler, banner='', local={}):
if 'exit' in local:
raise Exception('exit is reserved keyword')
import socket
try:
SocketServer.TCPServer.__init__(self, addr, handler)
except socket.error, e:
raise Exception('Socket error [%d]' % e.errno)
self.onLocalsChanged = Event()
self.allow_reuse_address = True
self.banner = banner
self.locals = local
self.__handlers_counter = 0
self.__lock = threading.Lock()
def run_async(self):
worker = threading.Thread(target=self.serve_forever)
worker.daemon = True
worker.start()
return worker
def update_locals(self, **kwargs):
for attr in kwargs:
self.locals[attr] = kwargs[attr]
self.onLocalsChanged.invoke(self.locals)
def exit(self):
try:
self.shutdown()
self.socket.close()
except Exception, e:
print e
@property
def isBusy(self):
return self.__handlers_counter > 1
@contextlib.contextmanager
def controller(self, handler):
with self.__lock:
self.onLocalsChanged += handler.updateLocals
self.__handlers_counter += 1
yield
with self.__lock:
self.__handlers_counter -= 1
self.onLocalsChanged -= handler.updateLocals
class __ConnectionHandler(SocketServer.StreamRequestHandler):
def handle(self):
self.__interpreter = Interpreter(self.server.locals)
with self.server.controller(self):
if self.server.isBusy:
self.send('Remote terminal is busy')
return
with std_redirector(self.rfile, self.wfile, self.wfile):
self.__interpreter.interact(banner=self.server.banner)
def send(self, data):
return self.wfile.write(data)
def updateLocals(self, new_locals):
if self.__interpreter:
self.__interpreter.updateLocals(new_locals)
def create_terminal(host, port, banner='', local={}):
server = __TerminalServer((host, port), __ConnectionHandler,
banner=banner, local=local)
server.daemon_threads = True
server.run_async()
return server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment