From ec37e36a622614ac233d986911889bac1e59fc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Mon, 7 Mar 2016 10:35:24 +0100 Subject: Cleaner output, input and output thread runs as daemons Output printing is now more standardized across script and allows defined Minecraft server like output. Input and output thread for server is now implemented more simple as daemons and wrapper exit is handled by waiting for server exit. Example configuration is little bit changed. It adds type information for all options. Vim configuration line added for both files (mcwrapper and example.conf). --- mcwrapper | 184 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 92 insertions(+), 92 deletions(-) (limited to 'mcwrapper') diff --git a/mcwrapper b/mcwrapper index fda83fa..1a2d2f0 100755 --- a/mcwrapper +++ b/mcwrapper @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# vim: expandtab ft=python ts=4 sw=4 sts=4: import os import sys import re @@ -19,30 +20,35 @@ _EC_ARG_MULTIPLE_CONFIG = 2 _EC_MISSING_CONFIGURATION = 10 _EC_SERVER_RUNNING = 11 -def info(message, minverbose = 0): +def __print_message__(message, file=sys.stdout, notime=False): + if notime: + print(message, file=file) + else: + print('[' + time.strftime('%H:%M:%S') + '] ' + message, file=file) + +def info(message, minverbose = 0, notime=False): "Prints message to stdout if minverbose >= verbose_level" try: if conf.verbose_level >= minverbose: - print(message) - except NameError: - print(message) + __print_message__(message, notime=notime) + except (NameError, TypeError): + __print_message__(message, notime=notime) -def warning(message, minverbose = -1): +def warning(message, minverbose = -1, notime=False): "Prints message to stderr if minverbose >= verbose_level" try: if conf.verbose_level >= minverbose: - print(message, file=sys.stderr) - except NameError: - print(message, file=sys.stderr) + __print_message__(message, file=sys.stderr, notime=notime) + except (NameError, TypeError): + __print_message__(message, file=sys.stderr, notime=notime) -def error(message, minverbose = -2, ec = -1): +def error(message, minverbose = -2, ec = -1, notime=False): "Prints message to stderr if minverbose >= verbose_level" try: if conf.verbose_level >= minverbose: - print(message, file=sys.stderr) - except NameError: - print(message, file=sys.stderr) - # TODO rather throw exception and handle it globally + __print_message__(message, file=sys.stderr, notime=notime) + except (NameError, TypeError): + __print_message__(message, file=sys.stderr, notime=notime) sys.exit(ec) ################################################################################# @@ -80,30 +86,47 @@ def load_conf(config_file): if not 'verbose_level' in vars(conf): conf.verbose_level = 0 -def conf_setserver(server): +def __conf_check_bad_type__(config): + error('Bad configuration type of configuration option: ' + config, + ec = _EC_MISSING_CONFIGURATION) +def __conf_check_missing__(config): + error('Missing configuration option: ' + config, + ec = _EC_MISSING_CONFIGURATION) +def __conf_check_no_dir__(directory): + error('No directory exists for configuration option: ' + directory, + ec = _EC_MISSING_CONFIGURATION) + +def conf_checkserver(server): "Check and set configuration for server specified as agument." try: srv = vars(conf)[server]; except KeyError: - error("No configuration class found for server: " + server, - ec = _EC_MISSING_CONFIGURATION) + error("No configuration class found", ec = _EC_MISSING_CONFIGURATION) if not 'timeout' in vars(srv): srv.timeout = 0 + if type(srv.timeout) != int: + __conf_check_bad_type__('timeout') if not 'directory' in vars(srv): - error('Missing "directory" config for server ' + server, - ec = _EC_MISSING_CONFIGURATION) + __conf_check_missing__('directory') + if type(srv.directory) != str: + __conf_check_bad_type__('directory') + srv.directory = os.path.expanduser(srv.directory) + if not os.path.isdir(srv.directory): + __conf_check_no_dir__('directory') if not 'command' in vars(srv): - error('Missing server start command for server ' + server, - ec = _EC_MISSING_CONFIGURATION) + __conf_check_missing__('command') + if type(srv.command) != str: + __conf_check_bad_type__('command') if not 'statusdir' in vars(srv): srv.statusdir = '/dev/shm/mcwrapper-' + server + if type(srv.statusdir) != str: + __conf_check_bad_type__('statusdir') + srv.statusdir = os.path.expanduser(srv.statusdir) return srv ################################################################################# # Minecraft server -_mcservers = [] - __STATUSSTRINGS__ = { 0: "Not running", 1: "Starting", @@ -112,21 +135,20 @@ __STATUSSTRINGS__ = { } class MCServer: - def __init__(self, identifier): - _mcservers.append(self) + def __init__(self, identifier, conf): self.identifier = identifier self.players = set() self.status = 0 - self.conf = conf_setserver(identifier) + self.conf = conf self.inputPipe = self.conf.statusdir + '/input_pipe' self.statusFile = self.conf.statusdir + '/status' self.playersFile = self.conf.statusdir + '/players' self.pidfile = self.conf.statusdir + '/server.pid' if type(self.conf.command) != str: self.conf.command = ' '.join(self.conf.command) - info(self.identifier + ": Server wrapper initializing") - info(self.identifier + ": Folder: " + self.conf.directory, 1) - info(self.identifier + ": Start command: " + self.conf.command, 1) + info("Server wrapper initializing") + info("Folder: " + self.conf.directory, 1) + info("Start command: " + self.conf.command, 1) try: os.mkdir(self.conf.statusdir) except FileExistsError: @@ -141,20 +163,21 @@ class MCServer: try: os.kill(lpid, 0) except OSError: - warning(self.identifier + ": Detected forced termination of " - "previous server wrapper instance.") + warning("Detected forced termination of previous server wrapper " + "instance.") else: - error(self.identifier + ": Another wrapper is running with given identifier.", + error("Another wrapper is running with given identifier.", -1, _EC_SERVER_RUNNING) with open(self.statusFile, 'w') as f: f.write(__STATUSSTRINGS__[0] + '\n') with open(self.playersFile, 'w') as f: pass - self.inputThread = __MCServerInputThread__(self) - self.outpuThread = __MCServerOutputThread__(self) - def __del__(self): - info(self.identifier + ": Server wrapper clean.") - _mcservers.remove(self) + self.inputThread = Thread(target=self.__input_thread__, + daemon = True) + self.outpuThread = Thread(target=self.__output_thread__, + daemon = True) + def clean(self): + info("Server wrapper clean.") try: os.remove(self.inputPipe) except FileNotFoundError: @@ -170,6 +193,10 @@ class MCServer: if os.path.isfile(self.pidfile): os.remove(self.pidfile) + def execstart(self): + "Start execution of server" + self.start() + self.prc.wait() def start(self): "Start Minecraft server" self.prc = subprocess.Popen(self.conf.command, stdin=subprocess.PIPE, @@ -180,12 +207,10 @@ class MCServer: self.status = 1 with open(self.statusFile, 'w') as f: f.write(__STATUSSTRINGS__[1] + '\n') - if self.inputThread.is_alive() or self.outpuThread.is_alive(): - # TODO throw exception - return - self.inputThread.start() - self.inputThread.wake() # Input thread is stuck in waiting for first line - self.outpuThread.start() + if not self.inputThread.is_alive(): + self.inputThread.start() + if not self.outpuThread.is_alive(): + self.outpuThread.start() def stop(self): if self.running(): self.prc.stdin.write(bytes("/stop\n", sys.getdefaultencoding())) @@ -200,37 +225,33 @@ class MCServer: def write_to_terminal(self, text): "Write to server terminal. If server not running it does nothing" if self.status == 2: - info(self.identifier + ": Input: " + text, 1) + info("Input: " + text, 1) self.prc.stdin.write(bytes(text, sys.getdefaultencoding())) self.prc.stdin.flush() return True else: return False - def join(self): - "Join execution untill server exits." - self.outpuThread.join() - self.inputThread.join() def __autoshutdown_enable__(self): if (self.conf.timeout > 0): - info(self.identifier + ": Automatic shutdown after " + str(self.conf.timeout) + " min.") + info("Automatic shutdown after " + str(self.conf.timeout) + " min.") self.shutdownTimeout = Timer(self.conf.timeout * 60.0, self.stop) self.shutdownTimeout.start(); def __autoshutdown_disable__(self): try: self.shutdownTimeout.cancel() del self.shutdownTimeout - info(self.identifier + ": Automatic shutdown disabled.") + info("Automatic shutdown disabled.") except AttributeError: pass def __user_join__(self, username): - info(self.identifier + ": User '" + username + "' joined server.") + info("User '" + username + "' joined server.") + self.players.add(username) with open(self.playersFile, 'a') as f: - self.players.add(username) f.write(username + '\n') self.__autoshutdown_disable__() def __user_leave__(self, username): - info(self.identifier + ": User '" + username + "' left server.") + info("User '" + username + "' left server.") self.players.remove(username) with open(self.playersFile, 'w') as f: f.writelines(self.players) @@ -241,13 +262,13 @@ class MCServer: def __parse_line__(self, line): if ': Done' in line: - info(self.identifier + ": Server start.") + info("Server start.") self.status = 2 with open(self.statusFile, 'w') as f: f.write(__STATUSSTRINGS__[2] + '\n') self.__autoshutdown_enable__() elif ': Stopping the server' in line: - info(self.identifier + ": Server stop.") + info("Server stop.") self.status = 3 with open(self.statusFile, 'w') as f: f.write(__STATUSSTRINGS__[3] + '\n') @@ -259,41 +280,21 @@ class MCServer: name = line[len('[00:00:00] [Server thread/INFO]: '):] name = name[:name.index(' ')] self.__user_leave__(name) - -class __MCServerOutputThread__(Thread): - def __init__(self, mcserver): - Thread.__init__(self, name='MCServerOutputThread:' + mcserver.identifier) - self.mcserver = mcserver - self.__stopread__ = False - def stop(self): - self.stopread = True - def run(self): - for linen in self.mcserver.prc.stdout: + def __output_thread__(self): + for linen in self.prc.stdout: line = linen.decode(sys.getdefaultencoding()) - info(self.mcserver.identifier + ": " + line.rstrip(), 2) - self.mcserver.__parse_line__(line.rstrip()) - self.mcserver.inputThread.stop() - self.mcserver.status = 0 - with open(self.mcserver.statusFile, 'w') as f: + info(line.rstrip(), 2, notime=True) + self.__parse_line__(line.rstrip()) + self.inputThread.stop() + self.status = 0 + with open(self.statusFile, 'w') as f: f.write(__STATUSSTRINGS__[0] + '\n') - -class __MCServerInputThread__(Thread): - def __init__(self, mcserver): - Thread.__init__(self, name='MCServerInputThread:' + mcserver.identifier) - self.mcserver = mcserver - self.stopread = False - def stop(self): - self.stopread = True - def wake(self): - with open(self.mcserver.inputPipe, 'w') as f: - f.write("\n") - f.flush() - def run(self): - with open(self.mcserver.inputPipe, 'r') as p: - while not self.stopread: + def __input_thread__(self): + with open(self.inputPipe, 'r') as p: + while True: ln = p.readline().rstrip() if ln: - self.mcserver.write_to_terminal(ln + "\n") + self.write_to_terminal(ln + "\n") else: time.sleep(3) @@ -303,13 +304,11 @@ class __MCServerInputThread__(Thread): def wrapper_atexit(): "This is called when wrapper is exiting" - for srv in _mcservers: - del srv + _mcserver.clean() def wrapper_toexit(): "This function is called when system signalizes that mcwrapper should exit" - for srv in _mcservers: - srv.stop() + _mcserver.stop() def __signal_term__(_signo, _stack_frame): wrapper_toexit() @@ -399,10 +398,11 @@ if __name__ == '__main__': elif not "identifier" in vars(conf): print_help() + server_conf = conf_checkserver(conf.identifier) + global _mcserver + _mcserver = MCServer(conf.identifier, server_conf) signal.signal(signal.SIGTERM, __signal_term__) signal.signal(signal.SIGINT, __signal_term__) atexit.register(wrapper_atexit) - mcs = MCServer(conf.identifier) - mcs.start() - mcs.join() + _mcserver.execstart() -- cgit v1.2.3