aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarel Kočí <cynerd@email.cz>2015-08-15 14:50:43 +0200
committerKarel Kočí <cynerd@email.cz>2015-09-03 12:56:29 +0200
commite4b0c7f50efbe0c42aa933cb58a86a44367c1140 (patch)
treef6d0a55e04f21ebf0be9a411a31ee8bb6be69628
parent8eb78a9a915cc2fb08905935bb1d26c1a808d4c2 (diff)
downloadmcserver-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.
-rw-r--r--example.conf12
-rwxr-xr-xmcwrapper488
-rw-r--r--modules/argmodules.py35
-rw-r--r--modules/list-modules.py51
-rw-r--r--modules/players.py52
-rw-r--r--modules/say.py49
-rw-r--r--modules/status.py71
-rw-r--r--modules/utils.py196
8 files changed, 801 insertions, 153 deletions
diff --git a/example.conf b/example.conf
new file mode 100644
index 0000000..d494f93
--- /dev/null
+++ b/example.conf
@@ -0,0 +1,12 @@
+# This is exaple configuration for mcwrapper
+# Use Python3 syntax to specify configuration.
+# For full list of configuration options refer to documentation.
+
+modules = {'say', 'argmodules', 'list-modules'}
+
+server = dict()
+server["exampleserver"] = {
+ "modules": {'status', 'players'},
+ "folder": '/dev/shm/mcwrapper-exampleserver',
+ "logOutput": False,
+ }
diff --git a/mcwrapper b/mcwrapper
index b3cf223..1b0c70d 100755
--- a/mcwrapper
+++ b/mcwrapper
@@ -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
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 = ()