diff options
author | Karel Kočí <cynerd@email.cz> | 2015-08-15 14:50:43 +0200 |
---|---|---|
committer | Karel Kočí <cynerd@email.cz> | 2015-09-03 12:56:29 +0200 |
commit | e4b0c7f50efbe0c42aa933cb58a86a44367c1140 (patch) | |
tree | f6d0a55e04f21ebf0be9a411a31ee8bb6be69628 /mcwrapper | |
parent | 8eb78a9a915cc2fb08905935bb1d26c1a808d4c2 (diff) | |
download | mcserver-wrapper-e4b0c7f50efbe0c42aa933cb58a86a44367c1140.tar.gz mcserver-wrapper-e4b0c7f50efbe0c42aa933cb58a86a44367c1140.tar.bz2 mcserver-wrapper-e4b0c7f50efbe0c42aa933cb58a86a44367c1140.zip |
Implemented module version of mcwrapper
mcwrapper functionality split to modules. This is basic implementation
of modules handling. Two module types are recognized. For server and
commands for mcwrapper cli interface. This way can be implemented
different command and server features simply without modifying main
script. Interface between main script and modules is defined using
service lists. Service list informs main script what function should
be called in module.
More detailed description should be written to README.md file. Or even
separated file describing module interface.
In this commit are implemented five different modules. Players and status
are server modules. They are used only if mcwrapper is running instance
of Minecraft server. Modules say and list-modules are implementing
mcwrapper actions. And last module argmodules is implementing mcwrapper
argument. For modules usage also added utils.py. This contains shared
usable code that is used even by main mcwrapper script.
Diffstat (limited to 'mcwrapper')
-rwxr-xr-x | mcwrapper | 488 |
1 files changed, 335 insertions, 153 deletions
@@ -3,111 +3,176 @@ import os import sys import re import subprocess -import datetime import signal import time +import datetime +import traceback from threading import Thread +import importlib.machinery as imp +################################################################################# +# Search for data folder -__log_prefix__ = '/home/minecraft/log/' -__folderprefix__ = '/dev/shm/' -__folder__ = __folderprefix__ -__players__ = 'players' -__status__ = 'status' -__pipe__ = 'input_pipe' -__log__ = 'none.log' - -__STATUSSTRINGS__ = { 0: "Not running", - 1: "Starting", - 2: "Running", - 3: "Stopping" - } - -def __setfiles__(identifier): - global __folder__ - global __players__ - global __status__ - global __pipe__ - global __log__ - __folder__ = __folderprefix__ + 'mcwrapper_' + identifier + '/' - __players__ = __folder__ + 'players' - __status__ = __folder__ + 'status' - __pipe__ = __folder__ + 'input_pipe' - __log__ = __log_prefix__ + identifier + '/' + \ - datetime.datetime.now().strftime('%y-%m-%d-%H-%M-%S') - -def __user_join__(username): - print("User '" + username + "' joined server.") - with open(__players__, 'a') as f: - f.write(username + '\n') - -def __user_leave__(username): - print("User '" + username + "' left server.") - players = set() - with open(__players__) as f: - for line in f: - pl = line.rstrip() - if pl != username: - players.add(line.rstrip()) - with open(__players__, 'w') as f: - f.writelines(players) - if players: - f.write('\n') +__all_data_folders__ = ( + os.path.dirname(__file__), + '.', + '~/mcwrapper', + '/usr/share/mcwrapper', + ) +def __is_data_folder__(path): + if os.path.isdir(path + '/modules') and \ + os.path.isfile(path + '/modules/utils.py'): + return True + else: + return False -def __server_init__(identifier): - __setfiles__(identifier) - __server_clean__() # Clean before execute - print("Wrapper initializing with identifier: " + identifier) +def __data_folder_missing__(path): + if not os.path.isdir(path + '/modules'): + print('Folder ' + path + 'modules not found.') + if not os.path.isfile(path + '/modules/utils.py'): + print('File ' + path + '/modules/utils.py not found.') + +__data_folder__ = None +try: + __data_folder__ = os.environ['DATAF'] + if not __is_data_folder__(__data_folder__): + print("Error: " + __data_folder__ + " doesn't seems to be mcwrapper data folder.", file=sys.stderr) + sys.exit(2) +except KeyError: + for df in __all_data_folders__: + if os.path.isdir(df): + __data_folder__ = df + break + if __data_folder__ == None: + print("Error: No mcwrapper data folder found.", file=sys.stderr) + sys.exit(1) + +################################################################################# +# Load and set utils + +utils = imp.SourceFileLoader("utils", + __data_folder__ + '/modules/utils.py').load_module() + +################################################################################# +# Load configuration + +__all_config_files__ = ( + 'mcwrapper.conf', + '~/.mcwrapper.conf', + '~/.config/mcwrapper.conf', + '/etc/mcwrapper.conf', + ) + +def __set_empty_config__(): + global conf + print('Warning: User configuration not loaded. Using default.', file=sys.stderr) + conf = type('defconf', (object,), {}) + +__config_file__ = None +try: + __config_file__ = os.environ['CONFIG'] +except KeyError: + for cf in __all_config_files__: + if os.path.isfile(cf): + __config_file__ = cf + break +if __config_file__ == None: + __set_empty_config__() +else: try: - os.mkdir(__folder__) + conf = imp.SourceFileLoader("conf", __config_file__).load_module() except Exception: - pass - if os.path.isfile(__status__): - sys.exit("Server status file already exists. Is another wrapper running?") - with open(__status__, 'w') as f: - f.write(__STATUSSTRINGS__[1]) - os.mkfifo(__pipe__, 0o640) - -def __server_start__(): - print("Server start.") - with open(__status__, 'w') as f: - f.write(__STATUSSTRINGS__[2]) - pass - -def __server_stop__(): - print("Server stop.") - with open(__status__, 'w') as f: - f.write(__STATUSSTRINGS__[3]) - pass + traceback.print_exc() + __set_empty_config__() -def __server_clean__(): - print("Wrapper clean.") +# Set conf to utils +utils.conf = conf + +utils.configSet(utils.__default_config__) +# Set additional runtime configuration variables +conf.verbose_level = 0 +conf.action = None +conf.action_module = None +conf.command = [] +conf.__modules_action__ = set() +conf.__modules_argument__ = set() +conf.modulesFolder = __data_folder__ + '/modules' + +################################################################################# +# Modules management + +def __module_load__(modname): try: - os.remove(__players__) - except Exception: - pass + module = imp.SourceFileLoader(modname, + conf.modulesFolder + '/' + modname + '.py').load_module() + return module + except FileNotFoundError: + if conf.verbose_level >= -2: + print("Error: Unknown module " + mod, file=sys.stderr) + sys.exit(3) + +def __module_unload__(mod): + for name, value in vars(conf): + if re.search('^__modules', name): + try: + value.remote(mod) + except KeyError: + pass + del mod + +# Load global modules +for mod in conf.modules: + module = __module_load__(mod) + if utils.Service.action in module.services: + conf.__modules_action__.add(module) + if utils.Service.argument in module.services: + conf.__modules_argument__.add(module) + +def server_modules_load(): + conf.__modules_config__ = set() + conf.__modules_init__ = set() + conf.__modules_clean__ = set() + conf.__modules_parse__ = set() + for mod in conf.modules: + if conf.verbose_level >= 1: + print('Loading module: ' + mod) + module = __module_load__(mod) + if utils.Service.config in module.services: + conf.__modules_config__.add(module) + if utils.Service.init in module.services: + conf.__modules_init__.add(module) + if utils.Service.clean in module.services: + conf.__modules_clean__.add(module) + if utils.Service.parse in module.services: + conf.__modules_parse__.add(module) + +################################################################################# + +def __server_init__(identifier): + if conf.verbose_level >= 0: + print("Wrapper initializing with identifier: " + identifier) try: - os.remove(__status__) - except Exception: + os.mkdir(conf.folder) + except FileExistsError: pass + if os.path.isfile(conf.inputPipe): + if conf.verbose_level >= 2: + print("Error: Server input pipe already exists. Is another wrapper running?") + sys.exit(4) + os.mkfifo(conf.inputPipe, 0o640) + utils.serviceCall('init', 'init') + +def __server_clean__(): + if conf.verbose_level >= 0: + print("Wrapper clean.") + utils.serviceCall('clean', 'clean') try: - os.remove(__pipe__) - except Exception: + os.remove(conf.inputPipe) + except FileNotFoundError: pass def __parse_line__(line): - if ': Done' in line: - __server_start__() - elif ': Stopping the server' in line: - __server_stop__() - elif 'logged in with entity id' in line: - name = line[len('[00:00:00] [Server thread/INFO]: '):] - name = name[:name.index('[')] - __user_join__(name) - elif 'left the game' in line: - name = line[len('[00:00:00] [Server thread/INFO]: '):] - name = name[:name.index(' ')] - __user_leave__(name) + utils.serviceCall('parse', 'parse', [line], 2) ################################################################################# @@ -120,22 +185,23 @@ class __InputThread__(Thread): def stopexec(self): self.stopread = True def wake(self): - with open(__pipe__, 'w') as f: + with open(self.pipein, 'w') as f: f.write("\n") f.flush() def run(self): with open(self.pipein, 'r') as p: while not self.stopread: ln = p.readline() - if ln: - print("Input: " + ln, end="") + if ln.rstrip(): + if conf.verbose_level >= 1: + print("Input: " + ln, end="") self.pipeprocess.write(bytes(ln, sys.getdefaultencoding())) self.pipeprocess.flush() else: time.sleep(1) try: - os.remove(__pipe__) - except Exception: + os.remove(conf.inputPipe) + except FileNotFoundError: pass def __server_send_stop__(): @@ -145,83 +211,199 @@ def __server_send_stop__(): def mcexec(identifier, cmd): """Executes cmd and parses output for server status changes. - - cmd - List of program and arguments to be executed. - logfile - Path in string. To entered file will be printed out full process output. - """ + """ global prc - __server_init__(identifier) - try: - os.makedirs(__log_prefix__ + identifier) - except Exception: - pass + __server_init__(conf.identifier) + if conf.logOutput: + try: + os.makedirs(os.path.dirname(conf.logFile)) + except FileExistsError: + pass + if type(cmd) != str: + cmd = ' '.join(cmd) + if conf.verbose_level >= 1: + print("Start command: " + cmd) prc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - inputThread = __InputThread__(__pipe__, prc.stdin) + stderr=subprocess.STDOUT, shell=True) + inputThread = __InputThread__(conf.inputPipe, prc.stdin) inputThread.start() - inputThread.wake() # Input thread is stack in waiting for first line + inputThread.wake() # Input thread is stuck in waiting for first line for linen in prc.stdout: line = linen.decode(sys.getdefaultencoding()) - with open(__log__, 'a') as flg: - flg.write(line) + if conf.verbose_level >= 2: + print(line.rstrip()) + if conf.logOutput: + with open(conf.logFile, 'a') as flg: + flg.write(line) __parse_line__(line.rstrip()) inputThread.stopexec() __server_clean__() ################################################################################# -def __get_status__(identifier): - if not os.path.isfile(__status__): - return 0 - else: - with open(__status__) as f: - status = f.readline().rstrip() - for key, val in __STATUSSTRINGS__.items(): - if val == status: - return key - -def server_stop(identifier): - __setfiles__(identifier) - status = __get_status__(identifier) - if status == 0: +def __signal_term__(_signo, _stack_frame): + __server_send_stop__() + +def action_start_exec(): + if not conf.identifier: + argument_help_exec() + return + server_modules_load() + utils.serviceCall('config', 'config', [conf]) + if not conf.command: + argument_help_exec() + return + signal.signal(signal.SIGTERM, __signal_term__) + signal.signal(signal.SIGINT, __signal_term__) + mcexec(conf.identifier, conf.command) + + +def action_stop_exec(): + if not conf.identifier: + argument_help_exec() + return + if not os.path.exists(conf.inputPipe): sys.exit("Such server is not running") - elif status == 1: - print("Server is starting. Waiting...") - while __get_status__(identifier) != 2: - time.sleep(1) - elif status == 3: - sys.exit("Server already stops") - with open(__pipe__, 'w') as f: + with open(conf.inputPipe, 'w') as f: f.write("/stop\n") f.flush() - while os.path.isfile(__status__): + while os.path.exists(conf.inputPipe): pass -def server_say(identifier, message): - __setfiles__(identifier) - if __get_status__(identifier) != 2: - sys.exit("Server is not running") - with open(__pipe__, 'w') as f: - f.write("/say " + ' '.join(map(str, message))) - f.flush() +def argument_help_exec(): + if conf.action_module == None: + if conf.action == 'start': + print('mcwrapper [arguments...] start IDENTIFIER {command...}') + print(' Start server under "IDENTIFIER" with command "command"') + print('') + print(' arguments') + utils.printArgumentsHelp() + print(' IDENTIFIER') + print(' Identifier for new server instance. This allows multiple server') + print(' instances running with this wrapper.') + print(' Identifier is word without spaces and preferably without special') + print(' characters.') + print(' command') + print(' Command to execute Minecraft server.') + elif conf.action == 'stop': + print('mcwrapper [arguments...] stop IDENTIFIER') + # TODO + else: + print('mcwrapper [arguments...] ACTION ...') + print(' This script is executing Minecraft server and reads its output.') + print('') + print(' arguments') + utils.printArgumentsHelp() + print('') + print(' ACTION') + print(' start') + print(' Starts server.') + print(' stop') + print(' Sends stop command to Minecraft server.') + for mod in conf.__modules_action__: + mod.action_help() + print('') + print('For more informations abou specific actions enter --help') + print('with ACTION argument.') + else: + conf.action_module.action_full_help() ################################################################################# -def __signal_term__(_signo, _stack_frame): - __server_send_stop__() - if __name__ == '__main__': - todo = sys.argv[1] - identifier = sys.argv[2] - if todo == 'start': - signal.signal(signal.SIGTERM, __signal_term__) - cmd = sys.argv[3:] - mcexec(identifier, cmd) - __server_clean__() - elif todo == 'stop': - server_stop(identifier) - elif todo == 'say': - message = sys.argv[3:] - server_say(identifier, message) + print_help = False + arguments = set() + i = 1 + while i < len(sys.argv): + arg = sys.argv[i] + if arg[0] == '-': + if len(arg) > 2 and arg[1] == '-': + cnt, mod = utils.serviceCall('argument', 'argument', [sys.argv[i:]], 2) + if cnt: + arguments.add(mod) + i += cnt + continue + if arg == '--help': + print_help = True + i += 1 + continue + if arg == '--verbose': + conf.verbose_level += 1 + i += 1 + continue + if arg == '--quiet': + conf.verbose_level += 1 + i += 1 + continue + else: + docontinue = False + i += 1 + for l in arg[1:]: + cnt, mod = utils.serviceCall('argument', 'argument_short', + [l, sys.argv[i:]], 2) + if cnt: + arguments.add(mod) + i += cnt + docontinue = True + elif l == 'h': + print_help = True + docontinue = True + elif l == 'v': + conf.verbose_level += 1 + docontinue = True + elif l == 'q': + conf.verbose_level -= 1 + docontinue = True + if docontinue: + continue + if conf.action == None: + rtn, mod = utils.serviceCall('action', 'action', [sys.argv[i:]], 2) + if rtn: + i += rtn + continue + if arg.lower() == 'start': + conf.action = 'start' + for arg in sys.argv[i+1:]: + if conf.identifier == None: + conf.identifier = arg + else: + if type(conf.command) == str: + conf.command += arg + else: + conf.command.append(arg) + break + if arg.lower() == 'stop': + conf.action = 'stop' + i += 1 + continue + else: + if conf.action_module == None: + if conf.action == 'stop': + if not conf.identifier: + conf.identifier = args[0] + i += 1 + continue + else: + cnt = conf.action_module.action(sys.argv[i:]) + if cnt: + i += cnt + continue + sys.exit("Unknown argument: " + arg) + + + if conf.identifier: + utils.setServerConf(conf.identifier) + for mod in arguments: + mod.argument_exec() + if print_help: + argument_help_exec() + sys.exit() + if conf.action_module != None: + conf.action_module.action_exec() else: - print("unknown action: " + todo) + if conf.action == 'start': + action_start_exec() + elif conf.action == 'stop': + action_stop_exec() + else: + pass # This shouldn't happen |