aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 = ()