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 /modules | |
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 'modules')
-rw-r--r-- | modules/argmodules.py | 35 | ||||
-rw-r--r-- | modules/list-modules.py | 51 | ||||
-rw-r--r-- | modules/players.py | 52 | ||||
-rw-r--r-- | modules/say.py | 49 | ||||
-rw-r--r-- | modules/status.py | 71 | ||||
-rw-r--r-- | modules/utils.py | 196 |
6 files changed, 454 insertions, 0 deletions
diff --git a/modules/argmodules.py b/modules/argmodules.py new file mode 100644 index 0000000..1b004f1 --- /dev/null +++ b/modules/argmodules.py @@ -0,0 +1,35 @@ +import re +import utils +from utils import conf + +services = ( + utils.Service.argument, + ) + +__add_modules__ = set() + +def argument(args): + global __add_modules__ + if not re.search('^--modules=', args[0]): + return 0 + __add_modules__ = args[0][10:].split(',') + return 1 + +def argument_short(l, args): + global __add_modules__ + if l == 'm': + if len(args) < 1: + return 0 + __add_modules__ = args[0].split(',') + return 1 + return 0 + +def argument_exec(): + for mod in __add_modules__: + conf.modules.add(mod) + +def argument_help(): + if conf.action == 'start' or conf.action == 'list-modules': + print(' -m MODULE,... --module=MODULE,...') + print(' Load additional server modules. Multiple modules can be') + print(' specified. Separate them using commas.') diff --git a/modules/list-modules.py b/modules/list-modules.py new file mode 100644 index 0000000..ca93dc3 --- /dev/null +++ b/modules/list-modules.py @@ -0,0 +1,51 @@ +import sys +import re +import utils +from utils import conf +import importlib.machinery as imp + +services = ( + utils.Service.action, + ) + +def action(args): + if conf.action == None: + if args[0].lower() != 'list-modules': + return False + conf.action = 'list-modules' + conf.action_module = sys.modules[__name__] + return 1 + elif conf.identifier == None: + conf.identifier = args[0] + return 1 + return 0 + +def action_exec(): + if conf.verbose_level >= 1: + for mod in conf.modules: + try: + module = imp.SourceFileLoader(mod, + conf.modulesFolder + '/' + mod + '.py').load_module() + print(module) + for service in module.services: + print(' ' + utils.Service.toStr(service)) + except FileNotFoundError: + sys.exit('Unknown module: ' + mod) + else: + # TODO add check if module exists + for mod in conf.modules: + print(mod) + +def action_help(): + print(' list-modules') + print(' List all modules that will be used.') + +def action_full_help(): + print('mcwrapper [arguments...] list-modules [IDENTIFIER]') + print(' List all modules that will be used.') + print('') + print(' arguments') + utils.printArgumentsHelp() + print(' IDENTIFIER') + print(' Identifier of Minecraft server instance.') + print(' If specified, server modules are printed.') diff --git a/modules/players.py b/modules/players.py new file mode 100644 index 0000000..91e50b6 --- /dev/null +++ b/modules/players.py @@ -0,0 +1,52 @@ +import os +import sys +import re +import utils +from utils import conf + +services = ( + utils.Service.config, + utils.Service.init, + utils.Service.clean, + utils.Service.parse + ) + +players = set() + +def config(conf): + conf.playersFile = conf.folder + '/players' + +def init(): + with open(conf.playersFile, 'w') as f: + pass + +def clean(): + os.remove(conf.playersFile) + +def parse(line): + if '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) + else: + return False + return True + + +def __user_join__(username): + print("User '" + username + "' joined server.") + with open(conf.playersFile, 'a') as f: + players.add(username) + f.write(username + '\n') + +def __user_leave__(username): + print("User '" + username + "' left server.") + players.remove(username) + with open(conf.playersFile, 'w') as f: + f.writelines(players) + if players: + f.write('\n') diff --git a/modules/say.py b/modules/say.py new file mode 100644 index 0000000..64810e0 --- /dev/null +++ b/modules/say.py @@ -0,0 +1,49 @@ +import sys +import re +import utils +from utils import conf + +services = ( + utils.Service.action, + ) + +def action(args): + if conf.action == None: + if args[0].lower() != 'say': + return False + conf.action = 'say' + conf.action_module = sys.modules[__name__] + conf.sayMessage = [] + for arg in args[1:]: + if conf.identifier == None: + conf.identifier = arg + else: + conf.sayMessage.append(arg) + return len(args) + else: + return 0 + +def action_exec(): + if not conf.sayMessage or not conf.identifier: + action_full_help() + return + if not utils.isServerRunning(): + sys.exit("Server is not running or wrong identifier.") + with open(sconf.inputPipe, 'w') as f: + f.write("/say " + ' '.join(map(str, sconf.saymessage)) + '\n') + f.flush() + +def action_help(): + print(' say') + print(' Sends message to Minecraft server chat.') + +def action_full_help(): + print('mcwrapper [arguments...] say IDENTIFIER {message...}') + print(' Sends message to Minecraft server chat.') + print('') + print(' arguments') + utils.printArgumentsHelp() + print(' IDENTIFIER') + print(' Identifier of running server instance.') + print(' message') + print(' Message to be send to Minecraft server chat.') diff --git a/modules/status.py b/modules/status.py new file mode 100644 index 0000000..906c1ec --- /dev/null +++ b/modules/status.py @@ -0,0 +1,71 @@ +import os +import sys +import re +import utils +from utils import conf + +services = ( + utils.Service.config, + utils.Service.init, + utils.Service.clean, + utils.Service.parse, + ) + +__STATUSSTRINGS__ = { + 0: "Not running", + 1: "Starting", + 2: "Running", + 3: "Stopping", + } + +def config(conf): + conf.statusFile = conf.folder + '/status' + +def init(): + with open(conf.statusFile, 'w') as f: + f.write(__STATUSSTRINGS__[1]) + +def clean(): + os.remove(conf.statusFile) + +def parse(line): + if ': Done' in line: + __server_start__() + elif ': Stopping the server' in line: + __server_stop__() + else: + return False + return True + +def __server_start__(): + print("Server start.") + with open(conf.statusFile, 'w') as f: + f.write(__STATUSSTRINGS__[2] + '\n') + pass + +def __server_stop__(): + print("Server stop.") + with open(conf.statusFile, 'w') as f: + f.write(__STATUSSTRINGS__[3] + '\n') + pass + +#### For other modules #### +def get_status(conf): + """Returns server status as number. + Requires conf (server configuration) set with identifier using utils.confset(). + Returns: + 0 - Not running + 1 - Starting + 2 - Running + 3 - Stopping + -1 - Unknown status + """ + conf.statusFile = conf.folder + '/status' + if not os.path.exists(conf.statusFile): + return 0 + with open(conf.statusFile, 'r') as f: + status = f.readline().rstrip() + for i in range(len(__STATUSSTRINGS__)): + if __STATUSSTRINGS__[i] == status: + return i + return -1 diff --git a/modules/utils.py b/modules/utils.py new file mode 100644 index 0000000..c8406ce --- /dev/null +++ b/modules/utils.py @@ -0,0 +1,196 @@ +# This is python file with usable utilities for modules +# This can't be used as mcwrapper module. Although it's not a problem if loaded. +import os +import datetime +import re +import struct +import traceback +from enum import Enum + +# Dummy variable to be used before it is set by mcwrapper +conf = type('defconf', (object,), {}) + +class Service(Enum): + # Request service of conf function + # This function is called right after argument parsing. + # Configuration is loaded before almost anything is done. + # Prototype: config(conf) + # Where conf is class containing configuration variables. + config = 1 + # Request service of init function + # This function is called right before Minecraft server is started. + # Prototype: init() + init = 2 + # Request service of clean function + # This function is called before mcwrapper exits. + # Prototype: clean() + clean = 3 + # Request service of parse function + # Prototype: parse(line) + # Where line is line from Minecraft server standard and error output. + parse = 4 + # Signalize that exceptions shouldn't be ignored. + # Otherwise exception is printed and module is removed. + exceptionThrow = 101 + # Requests service of action and action_help function. + # This flag can't be denied by serviceServer or by configuration. + # Prototype: action(act, args) + # Where act is string specifying action and args are rest of command line + # arguments. + # Prototype: action_help() + action = 201 + # Requests service of argument function. + # This flag can't be denied by serviceServer or by configuration. + # Prototype: argument(arg, args) + # Where arg is parser argument and args are rest of command line arguments. + argument = 202 + def toStr(service): + if service == Service.config: + return 'S-Config' + elif service == Service.init: + return 'S-Init' + elif service == Service.clean: + return 'S-Clean' + elif service == Service.parse: + return 'S-Parse' + elif service == Service.exceptionThrow: + return 'F-ExceptionThrow' + elif service == Service.action: + return 'P-Action' + elif service == Service.argument: + return 'P-Argument' + +def __module_disable__(module): + """Disable specified module""" + if verbose_level >= 0: + print('Disabling module: ' + str(module)) + if Service.clean in module.services: + try: + module.clean() + except Exception: + traceback.print_exc() + for name, value in vars(conf).items(): + if re.search('^__modules', name): + try: + value.remove(module) + except KeyError: + pass + del module + +def printArgumentsHelp(): + """Prints help for all arguments from loaded modules""" + print(' -h, --help') + print(' Prints this help text.') + print(' -v, --verbose') + print(' Increase verbose level of output.') + print(' -q, --quiet') + print(' Decrease verbose level of output.') + for mod in conf.__modules_argument__: + mod.argument_help() + +def serviceCall(servicename, func, argv=[], mode=0): + """Calls func in all/n-th modules with specified service. + + servicename - String name of service + func - String name of functions to be called + argv - List of arguments passed to functions + mode - Mode of execution + 0 - called for every module without result returning + 1 - called for every module and return result + 2 - called until True is returned from function called + """ + def execmod(servicename, func, argv, mod): + cmd = 'mod.' + func + '( ' + for i in range(0, len(argv)): + cmd += 'argv[' + str(i) + '],' + cmd = cmd[0:len(cmd)-1] + ')' + try: + return eval(cmd) + except Exception as e: + if Service.exceptionThrow in mod.services: + raise e + else: + traceback.print_exc() + __module_disable__(mod) + return None + if mode == 0: + for mod in vars(conf)['__modules_' + servicename + '__'].copy(): + execmod(servicename, func, argv, mod) + return + elif mode == 1: + ret = dict() + for mod in vars(conf)['__modules_' + servicename + '__'].copy(): + ret[mod] = execmod(servicename, func, argv, mod) + return ret + elif mode == 2: + for mod in vars(conf)['__modules_' + servicename + '__'].copy(): + rtn = execmod(servicename, func, argv, mod) + if rtn: + return rtn, mod + return None, None + +def isServerRunning(): + """Check if server is running. It checks if input_pipe exists. + Returns: + True - Running in any state or residue pipe exists. + False - Not running + """ + return os.path.exists(conf.inputPipe) + +__default_config__ = { + "modules": {'say', 'argmodules', 'list-modules', 'printconf'}, + "identifier": None, + } +__default_server_config__ = { + "modules": {'status', 'players'}, + "folder": '/dev/shm/mcwrapper-exampleserver', + "logOutput": False, + "logFile": datetime.datetime.now().strftime('%y-%m-%d-%H-%M-%S') + '.log', + "command": [], + } + +def setServerConf(identifier): + """Sets server configuration.""" + conf.identifier = identifier + try: + conf.server[identifier] + vars(conf).update(conf.server[identifier]) + except AttributeError: + if conf.verbose_level >= 0: + print('W: No configuration associated with identifier: "' + conf.identifier) + configSet(__default_server_config__) + # Set additional runtime configuration variables + conf.inputPipe = conf.folder + '/input_pipe' + +def configSet(confs): + """This is for setting default configurations. If configuration for module is + not set in conf file, then it must be set while module initialization. + + confs - dictionary of configuration options and default values. + """ + for name, val in confs.items(): + try: + dir(conf).index(name) + except ValueError: + exec('conf.' + name + '=val') + +def varint_unpack(data): + "Returns varint value from beginning of data and number of bytes used." + i = 0 + nextbt = True + newdata = 0 + while nextbt: + bt = data[i] + if not bt & (1 << 7): + nextbt = False + bt = bt & ~(1 << 7) + newdata = newdata | (bt << (i * 7)) + print(newdata) + return newdata, i + +def varint_pack(integer): + pass + +################################################################################# +## dummy module +services = () |