aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--example.conf45
-rwxr-xr-xmcwrapper184
3 files changed, 120 insertions, 113 deletions
diff --git a/README.md b/README.md
index a9c7599..f09895b 100644
--- a/README.md
+++ b/README.md
@@ -52,8 +52,8 @@ Status can be:
If file not exists, then server is not running at all.
###Players file
-This file in in status directory named as `players`. If server is running, it
-contains online players. Player name per line. If server isn't running, it
+This file in in status directory and is named as `players`. If server is running,
+it contains online players. Player name per line. If server isn't running, it
content don't have to be valid.
###Input pipe
diff --git a/example.conf b/example.conf
index a267735..9d656ea 100644
--- a/example.conf
+++ b/example.conf
@@ -1,29 +1,36 @@
-# This is exaple configuration for mcwrapper
+# This is example configuration for mcwrapper
# Use Python3 syntax to specify configuration.
##############################################
+# vim: expandtab ft=python ts=4 sw=4 sts=4:
# This is default identifier. It is used if no identifier is provided as argument
# to mcwrapper. This is specially handy when you have only one identifier to be
# run all the time.
# Uncommenting this option if you want such feature.
+# Type: string
#identifier = 'exampleserver'
+# Definition of Minecraft server.
class exampleserver:
- # Directory in which Minecraft server will be executed. It is where its files
- # will be placed
- directory = '~/minecraft',
- # Command to start Minecraft server. It is executed in directory specified in
- # option "directory".
- # Suggested is to always append "nogui" no disable graphical interface.
- command = "java -jar mcs.jar nogui",
- # Directory where wrapper writes files signaling status of server and online
- # players.
- # In default it points to directory in /dev/shm. This means that files are in
- # such case stored only in ram.
- statusdir = '/dev/shm/mcwrapper-exampleserver',
- # Automatic server shutdown when no player is online. This option defines time
- # in minutes before that happens. It is measured from time of last player
- # leaving server.
- # Set this value to less or equal zero or comment it to disable automatic
- # shutdown.
- timeout = 15
+ # Directory in which Minecraft server will be executed. It is where its files
+ # will be placed
+ # Type: string
+ directory = '~/minecraft'
+ # Command to start Minecraft server. It is executed in directory specified in
+ # option "directory".
+ # Suggested is to always append "nogui" no disable graphical interface.
+ # Type: string
+ command = "java -jar mcs.jar nogui"
+ # Directory where wrapper writes files signaling status of server and online
+ # players.
+ # In default it points to directory in /dev/shm. This means that files are in
+ # such case stored only in ram.
+ # Type: string
+ statusdir = '/dev/shm/mcwrapper-exampleserver'
+ # Automatic server shutdown when no player is online. This option defines time
+ # in minutes before that happens. It is measured from time of last player
+ # leaving server.
+ # Set this value to less or equal zero or comment it to disable automatic
+ # shutdown.
+ # Type: int
+ timeout = 15
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()