# vim: expandtab ft=python ts=4 sw=4 sts=4: import os import sys import subprocess import time from threading import Thread from .import prints __INPUTPIPE__ = 'input_pipe' __PIDFILE__ = 'server.pid' class MCWrapper: "Minecraft server wrapper class" def __init__(self, command): self.process = None self.command = command self._running = False self._hook_start = [] self._hook_stop = [] self._hook_line = [] prints.info("Server wrapper initializing") if os.path.isfile(__PIDFILE__): with open(__PIDFILE__) as file: lpid = int(file.readline()) try: os.kill(lpid, 0) except OSError: prints.warning("Detected forced termination of previous server" " wrapper instance.") else: prints.error("Another wrapper is running with given " "identifier.", -1, 1) try: os.mkfifo(__INPUTPIPE__, 0o640) except FileExistsError: pass self.inputthread = Thread(target=self.__input_thread__, daemon=True) self.outputhread = Thread(target=self.__output_thread__, daemon=True) def clean(self): "Cleans files generated by wrapper" prints.info("Server wrapper clean.") try: os.remove(__INPUTPIPE__) except FileNotFoundError: pass try: os.remove(__PIDFILE__) except FileNotFoundError: pass def start(self): "Start Minecraft server" self.process = subprocess.Popen( self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, start_new_session=False) for h in self._hook_start: h() self._running = True with open(__PIDFILE__, "w") as file: file.write(str(self.process.pid)) if not self.inputthread.is_alive(): self.inputthread.start() if not self.outputhread.is_alive(): self.outputhread.start() def stop(self): "Sends /stop command to Minecraft server" if self.running(): self.process.stdin.write(bytes( "/stop\n", sys.getdefaultencoding())) self.process.stdin.flush() self._running = False def running(self): "Returns True if mc server is running. Othervise False." return True def write_to_terminal(self, text): "Write to server terminal. If server not running it does nothing" if self._running: prints.info("Input: " + text.rstrip(), 1) self.process.stdin.write(bytes(text, sys.getdefaultencoding())) self.process.stdin.flush() return True else: return False def hook_start(self, handler): self._hook_start.append(handler) def hook_stop(self, handler): self._hook_stop.append(handler) def hook_line(self, contains, handler): n = dict() n["contains"] = contains n["handler"] = handler self._hook_line.append(n) def __parse_line__(self, line): i = 0 while i < len(self._hook_line): if self._hook_line[i]["contains"] in line: self._hook_line[i]["handler"](line) i += 1 def __output_thread__(self): for linen in self.process.stdout: line = linen.decode(sys.getdefaultencoding()) prints.info(line.rstrip(), 2, notime=True) self.__parse_line__(line.rstrip()) def __input_thread__(self): with open(__INPUTPIPE__, 'r') as pipe: while True: line = pipe.readline().rstrip() # TODO use polling if line: self.write_to_terminal(line + "\n") else: time.sleep(3)