From 2a8ad733fb5dc2e763256207332e42c90a3d6a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Thu, 31 Mar 2016 00:29:17 +0200 Subject: Setting up Python package Adding support for installing using pip. This will be default way to install mcwrapper. --- .gitignore | 1 + .travis.yml | 8 -- README.md | 1 - mcwrapper | 272 ---------------------------------------------- mcwrapper/__init__.py | 76 +++++++++++++ mcwrapper/prints.py | 32 ++++++ mcwrapper/wrapper.py | 175 +++++++++++++++++++++++++++++ setup.py | 35 ++++++ tests/all.sh | 5 - tests/prepare.sh | 27 ----- tests/t_codingstandard.sh | 24 ---- 11 files changed, 319 insertions(+), 337 deletions(-) delete mode 100644 .travis.yml delete mode 100755 mcwrapper create mode 100755 mcwrapper/__init__.py create mode 100644 mcwrapper/prints.py create mode 100644 mcwrapper/wrapper.py create mode 100755 setup.py delete mode 100755 tests/all.sh delete mode 100755 tests/prepare.sh delete mode 100755 tests/t_codingstandard.sh diff --git a/.gitignore b/.gitignore index 2a37342..29d0c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ .* __*__ +*.egg-info !.gitignore !.travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3831523..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: python -python: "3.5" -install: - - "pip install pep8" - - "pip install pyflakes" - - "sudo apt-get install openjdk-7-jre" - -script: tests/all.sh diff --git a/README.md b/README.md index 0c8ce72..8027eaf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ MCSERVER-WRAPPER ================ -[![Build Status](https://travis-ci.org/Cynerd/minecraft-wrapper.svg?branch=master)](https://travis-ci.org/Cynerd/minecraft-wrapper) Minecraft server wrapper written in Python3 that extracts server status and list of online players. diff --git a/mcwrapper b/mcwrapper deleted file mode 100755 index e65a0f5..0000000 --- a/mcwrapper +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env python3 -# vim: expandtab ft=python ts=4 sw=4 sts=4: -import os -import sys -import subprocess -import signal -import time -import atexit -import argparse -from threading import Thread -############################################################################### -# Exit codes and prints helpers -verbose_level = 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" - if verbose_level >= minverbose: - __print_message__(message, notime=notime) - - -def warning(message, minverbose=-1, notime=False): - "Prints message to stderr if minverbose >= verbose_level" - if verbose_level >= minverbose: - __print_message__(message, file=sys.stderr, notime=notime) - - -def error(message, minverbose=-2, errcode=-1, notime=False): - "Prints message to stderr if minverbose >= verbose_level" - if verbose_level >= minverbose: - __print_message__(message, file=sys.stderr, notime=notime) - sys.exit(errcode) - -############################################################################### - -__STATUSSTRINGS__ = { - 0: "Not running", - 1: "Starting", - 2: "Running", - 3: "Stopping", - } - -__INPUTPIPE__ = 'input_pipe' -__STATUSFILE__ = 'status' -__PLAYERSFILE__ = 'players' -__PIDFILE__ = 'server.pid' - - -class MCServer: - "Minecraft server wrapper class" - def __init__(self, command, statusfile=False, playersfile=False): - self.players = set() - self.status = 0 - self.process = None - self.command = command - self.statusfile = statusfile - self.plaersfile = playersfile - info("Server wrapper initializing") - if os.path.isfile(__PIDFILE__): - with open(__PIDFILE__) as file: - lpid = int(file.readline()) - try: - os.kill(lpid, 0) - except OSError: - warning("Detected forced termination of previous server " - "wrapper instance.") - else: - error("Another wrapper is running with given identifier.", - -1, 1) - try: - os.mkfifo(__INPUTPIPE__, 0o640) - except FileExistsError: - pass - if statusfile: - with open(__STATUSFILE__, 'w') as file: - file.write(__STATUSSTRINGS__[0] + '\n') - if playersfile: - open(__PLAYERSFILE__, 'w') - self.inputthread = Thread(target=self.__input_thread__, - daemon=True) - self.outputhread = Thread(target=self.__output_thread__, - daemon=True) - - def clean(self): - "Cleans files generated by wrapper" - info("Server wrapper clean.") - try: - os.remove(__INPUTPIPE__) - except FileNotFoundError: - pass - try: - os.path.isfile(__PIDFILE__) - except FileNotFoundError: - pass - try: - os.remove(__STATUSFILE__) - except FileNotFoundError: - pass - try: - os.remove(__STATUSFILE__) - except FileNotFoundError: - pass - - def execstart(self): - "Start execution of Minecraft server and hold until its exits" - self.start() - self.process.wait() - - def start(self): - "Start Minecraft server" - self.process = subprocess.Popen( - self.command, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - start_new_session=False) - with open(__PIDFILE__, "w") as file: - file.write(str(self.process.pid)) - self.status = 1 - if self.statusfile: - with open(__STATUSFILE__, 'w') as file: - file.write(__STATUSSTRINGS__[1] + '\n') - if not self.inputthread.is_alive(): - self.inputthread.start() - if not self.outputhread.is_alive(): - self.outputhread.start() - - def stop(self): - "Sends /stop command to Minecraft server" - if self.running(): - self.process.stdin.write(bytes( - "/stop\n", sys.getdefaultencoding())) - self.process.stdin.flush() - - def running(self): - "Returns True if mc server is running. Othervise False." - return bool(self.status) - - def write_to_terminal(self, text): - "Write to server terminal. If server not running it does nothing" - if self.status == 2: - info("Input: " + text, 1) - self.process.stdin.write(bytes(text, sys.getdefaultencoding())) - self.process.stdin.flush() - return True - else: - return False - - def __user_join__(self, username): - info("User '" + username + "' joined server.") - self.players.add(username) - if self.plaersfile: - with open(__PLAYERSFILE__, 'a') as file: - file.write(username + '\n') - - def __user_leave__(self, username): - info("User '" + username + "' left server.") - self.players.remove(username) - if self.plaersfile: - with open(__PLAYERSFILE__, 'w') as file: - file.writelines(self.players) - if self.players: - file.write('\n') - - def __parse_line__(self, line): - if ': Done' in line: - info("Server start.") - self.status = 2 - if self.statusfile: - with open(__STATUSFILE__, 'w') as file: - file.write(__STATUSSTRINGS__[2] + '\n') - elif ': Stopping the server' in line: - info("Server stop.") - self.status = 3 - if self.statusfile: - with open(__STATUSFILE__, 'w') as file: - file.write(__STATUSSTRINGS__[3] + '\n') - elif 'logged in with entity id' in line: - name = line[len('[00:00:00] [Server thread/INFO]: '):] - name = name[:name.index('[')] - self.__user_join__(name) - elif 'left the game' in line: - name = line[len('[00:00:00] [Server thread/INFO]: '):] - name = name[:name.index(' ')] - self.__user_leave__(name) - - def __output_thread__(self): - for linen in self.process.stdout: - line = linen.decode(sys.getdefaultencoding()) - info(line.rstrip(), 2, notime=True) - self.__parse_line__(line.rstrip()) - if self.statusfile: - with open(__STATUSFILE__, 'w') as file: - file.write(__STATUSSTRINGS__[0] + '\n') - - def __input_thread__(self): - with open(__INPUTPIPE__, 'r') as pipe: - while True: - line = pipe.readline().rstrip() - if line: - self.write_to_terminal(line + "\n") - else: - time.sleep(3) - -############################################################################### -mcserver = None - - -def __wrapper_atexit__(): - "This is called when wrapper is exiting" - mcserver.clean() - - -def __wrapper_toexit__(): - "This function is called when system signalizes that mcwrapper should exit" - mcserver.stop() - - -def __signal_term__(_signo, _stack_frame): - __wrapper_toexit__() - - -__HELP_DESC__ = """ - This script is executing Minecraft server and reads its output. From output - is extracted server status and list of online players. And standard input - can be accessed by fifo file. - """ - - -def main(): - "Main function" - global verbose_level - parser = argparse.ArgumentParser(description=__HELP_DESC__) - parser.add_argument('--verbose', '-v', action='count', default=0, - help="Increase verbose level of output") - parser.add_argument('--quiet', '-q', action='count', default=0, - help="Decrease verbose level of output") - parser.add_argument('--status-file', '-s', action='store_true', - help="Outputs server status to file \"status\"") - parser.add_argument('--players-file', '-p', action='store_true', - help="""Outputs list of online players to file - \"players\" """) - parser.add_argument('command', nargs=argparse.REMAINDER, - help="""Command to be executed to start Minecraft - server.""") - args = parser.parse_args() - - verbose_level = args.verbose - args.quiet - command = args.command - sfile = args.status_file - pfile = args.players_file - - if not command: - parser.print_help() - if 'nogui' not in command: - command.append('nogui') - - global mcserver - mcserver = MCServer(command, pfile, sfile) - signal.signal(signal.SIGTERM, __signal_term__) - signal.signal(signal.SIGINT, __signal_term__) - atexit.register(__wrapper_atexit__) - - mcserver.execstart() - -if __name__ == '__main__': - main() diff --git a/mcwrapper/__init__.py b/mcwrapper/__init__.py new file mode 100755 index 0000000..a1fed68 --- /dev/null +++ b/mcwrapper/__init__.py @@ -0,0 +1,76 @@ +# vim: expandtab ft=python ts=4 sw=4 sts=4: +import os +import sys +import subprocess +import signal +import time +import atexit +import argparse +from threading import Thread + +from . import prints +from . import wrapper +from .wrapper import MCWrapper + +mcserver_wrapper = None + + +def __wrapper_atexit__(): + "This is called when wrapper is exiting" + mcserver_wrapper.clean() + + +def __wrapper_toexit__(): + "This function is called when system signalizes that mcwrapper should exit" + mcserver_wrapper.stop() + + +def __signal_term__(_signo, _stack_frame): + __wrapper_toexit__() + + +__HELP_DESC__ = """ + This script is executing Minecraft server and reads its output. From output + is extracted server status and list of online players. And standard input + can be accessed by fifo file. + """ + + +def main(): + "Main function" + global verbose_level + parser = argparse.ArgumentParser(description=__HELP_DESC__) + parser.add_argument('--verbose', '-v', action='count', default=0, + help="Increase verbose level of output") + parser.add_argument('--quiet', '-q', action='count', default=0, + help="Decrease verbose level of output") + parser.add_argument('--status-file', '-s', action='store_true', + help="Outputs server status to file \"status\"") + parser.add_argument('--players-file', '-p', action='store_true', + help="""Outputs list of online players to file + \"players\" """) + parser.add_argument('command', nargs=argparse.REMAINDER, + help="""Command to be executed to start Minecraft + server.""") + args = parser.parse_args() + + prints.verbose_level = args.verbose - args.quiet + command = args.command + sfile = args.status_file + pfile = args.players_file + + if not command: + parser.print_help() + if 'nogui' not in command: + command.append('nogui') + + global mcserver_wrapper + mcserver_wrapper = MCWrapper(command, pfile, sfile) + signal.signal(signal.SIGTERM, __signal_term__) + signal.signal(signal.SIGINT, __signal_term__) + atexit.register(__wrapper_atexit__) + + mcserver_wrapper.execstart() + +if __name__ == '__main__': + main() diff --git a/mcwrapper/prints.py b/mcwrapper/prints.py new file mode 100644 index 0000000..cb19358 --- /dev/null +++ b/mcwrapper/prints.py @@ -0,0 +1,32 @@ +# vim: expandtab ft=python ts=4 sw=4 sts=4: +import sys +import time + +verbose_level = 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" + if verbose_level >= minverbose: + __print_message__(message, notime=notime) + + +def warning(message, minverbose=-1, notime=False): + "Prints message to stderr if minverbose >= verbose_level" + if verbose_level >= minverbose: + __print_message__(message, file=sys.stderr, notime=notime) + + +def error(message, minverbose=-2, errcode=-1, notime=False): + "Prints message to stderr if minverbose >= verbose_level" + if verbose_level >= minverbose: + __print_message__(message, file=sys.stderr, notime=notime) + sys.exit(errcode) + diff --git a/mcwrapper/wrapper.py b/mcwrapper/wrapper.py new file mode 100644 index 0000000..1896266 --- /dev/null +++ b/mcwrapper/wrapper.py @@ -0,0 +1,175 @@ +# vim: expandtab ft=python ts=4 sw=4 sts=4: +import os +import sys +import subprocess +import time +from threading import Thread + +from .import prints + +__STATUSSTRINGS__ = { + 0: "Not running", + 1: "Starting", + 2: "Running", + 3: "Stopping", + } + +__INPUTPIPE__ = 'input_pipe' +__STATUSFILE__ = 'status' +__PLAYERSFILE__ = 'players' +__PIDFILE__ = 'server.pid' + + +class MCWrapper: + "Minecraft server wrapper class" + def __init__(self, command, statusfile=False, playersfile=False): + self.players = set() + self.status = 0 + self.process = None + self.command = command + self.statusfile = statusfile + self.plaersfile = playersfile + prints.info("Server wrapper initializing") + if os.path.isfile(__PIDFILE__): + with open(__PIDFILE__) as file: + lpid = int(file.readline()) + try: + os.kill(lpid, 0) + except OSError: + prints.warning("Detected forced termination of previous server " + "wrapper instance.") + else: + prints.error("Another wrapper is running with given identifier.", + -1, 1) + try: + os.mkfifo(__INPUTPIPE__, 0o640) + except FileExistsError: + pass + if statusfile: + with open(__STATUSFILE__, 'w') as file: + file.write(__STATUSSTRINGS__[0] + '\n') + if playersfile: + open(__PLAYERSFILE__, 'w') + self.inputthread = Thread(target=self.__input_thread__, + daemon=True) + self.outputhread = Thread(target=self.__output_thread__, + daemon=True) + + def clean(self): + "Cleans files generated by wrapper" + prints.info("Server wrapper clean.") + try: + os.remove(__INPUTPIPE__) + except FileNotFoundError: + pass + try: + os.remove(__PIDFILE__) + except FileNotFoundError: + pass + try: + os.remove(__STATUSFILE__) + except FileNotFoundError: + pass + try: + os.remove(__STATUSFILE__) + except FileNotFoundError: + pass + + def execstart(self): + "Start execution of Minecraft server and hold until its exits" + self.start() + self.process.wait() + + def start(self): + "Start Minecraft server" + self.process = subprocess.Popen( + self.command, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + start_new_session=False) + with open(__PIDFILE__, "w") as file: + file.write(str(self.process.pid)) + self.status = 1 + if self.statusfile: + with open(__STATUSFILE__, 'w') as file: + file.write(__STATUSSTRINGS__[1] + '\n') + if not self.inputthread.is_alive(): + self.inputthread.start() + if not self.outputhread.is_alive(): + self.outputhread.start() + + def stop(self): + "Sends /stop command to Minecraft server" + if self.running(): + self.process.stdin.write(bytes( + "/stop\n", sys.getdefaultencoding())) + self.process.stdin.flush() + + def running(self): + "Returns True if mc server is running. Othervise False." + return bool(self.status) + + def write_to_terminal(self, text): + "Write to server terminal. If server not running it does nothing" + if self.status == 2: + prints.info("Input: " + text, 1) + self.process.stdin.write(bytes(text, sys.getdefaultencoding())) + self.process.stdin.flush() + return True + else: + return False + + def __user_join__(self, username): + prints.info("User '" + username + "' joined server.") + self.players.add(username) + if self.plaersfile: + with open(__PLAYERSFILE__, 'a') as file: + file.write(username + '\n') + + def __user_leave__(self, username): + prints.info("User '" + username + "' left server.") + self.players.remove(username) + if self.plaersfile: + with open(__PLAYERSFILE__, 'w') as file: + file.writelines(self.players) + if self.players: + file.write('\n') + + def __parse_line__(self, line): + if ': Done' in line: + prints.info("Server start.") + self.status = 2 + if self.statusfile: + with open(__STATUSFILE__, 'w') as file: + file.write(__STATUSSTRINGS__[2] + '\n') + elif ': Stopping the server' in line: + prints.info("Server stop.") + self.status = 3 + if self.statusfile: + with open(__STATUSFILE__, 'w') as file: + file.write(__STATUSSTRINGS__[3] + '\n') + elif 'logged in with entity id' in line: + name = line[len('[00:00:00] [Server thread/INFO]: '):] + name = name[:name.index('[')] + self.__user_join__(name) + elif 'left the game' in line: + name = line[len('[00:00:00] [Server thread/INFO]: '):] + name = name[:name.index(' ')] + self.__user_leave__(name) + + def __output_thread__(self): + for linen in self.process.stdout: + line = linen.decode(sys.getdefaultencoding()) + prints.info(line.rstrip(), 2, notime=True) + self.__parse_line__(line.rstrip()) + if self.statusfile: + with open(__STATUSFILE__, 'w') as file: + file.write(__STATUSSTRINGS__[0] + '\n') + + def __input_thread__(self): + with open(__INPUTPIPE__, 'r') as pipe: + while True: + line = pipe.readline().rstrip() + if line: + self.write_to_terminal(line + "\n") + else: + time.sleep(3) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..d2a6baa --- /dev/null +++ b/setup.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +from setuptools import setup, find_packages +from os import path + +here = path.abspath(path.dirname(__file__)) +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + + +setup( + name='mcserver-wrapper', + version='0.3.0', + description="Minecraft server wrapper", + long_description=long_description, + url="https://github.com/Cynerd/mcserver-wrapper", + author="Cynerd", + author_email="cynerd@email.cz", + license="GPLv2", + + clasifiers=[ + 'Development Status :: 3 - Alpha', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + ], + keywords='Minecraft wrapper server', + + packages=['mcwrapper'], + entry_points={ + 'console_scripts': [ + 'mcwrapper=mcwrapper:main' + ] + } + ) diff --git a/tests/all.sh b/tests/all.sh deleted file mode 100755 index 1237154..0000000 --- a/tests/all.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -cd "$( dirname "${BASH_SOURCE[0]}" )" - -./t_codingstandard.sh -[[ ! $? -ne 0 ]] || exit 1 diff --git a/tests/prepare.sh b/tests/prepare.sh deleted file mode 100755 index 17e466d..0000000 --- a/tests/prepare.sh +++ /dev/null @@ -1,27 +0,0 @@ -#~/bin/bash -if [[ "$(basename -- "$0")" = "prepare.sh" ]]; then - echo "Please only source this script" - exit 1 -fi - -if [[ $PREPARED != "y" ]]; then - # Move to known directory - cd "$( readlink -f "${BASH_SOURCE[0]}" )" - - if [[ $MCSERVERS == "y" ]]; then - mkdir -p minecraft-server - echo "eula=true" > minecraft-server/eula.txt - # Get Minecraft 1.8.8 - [ -f minecraft-server/minecraft_server.1.8.8.jar ] || \ - wget https://s3.amazonaws.com/Minecraft.Download/versions/1.8.8/minecraft_server.1.8.8.jar -O minecraft-server/minecraft_server.1.8.8.jar - # Get Minecraft 1.9 - [ -f minecraft-server/minecraft_server.1.9.jar ] || \ - wget https://s3.amazonaws.com/Minecraft.Download/versions/1.9/minecraft_server.1.9.jar -O minecraft-server/minecraft_server.1.9.jar - function mcservers_clean { - find minecraft-server | tail -n +2 | egrep -v "minecraft_server.*jar" | egrep -v "eula.txt" | xargs rm -rf - } - fi - - export PATH=$( realpath .. ):$PATH - export PREPARED="y" -fi diff --git a/tests/t_codingstandard.sh b/tests/t_codingstandard.sh deleted file mode 100755 index 1b472b4..0000000 --- a/tests/t_codingstandard.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -cd "$( dirname "${BASH_SOURCE[0]}" )" -source prepare.sh - -echo "Checking code style using pep8" -pep8 ../mcwrapper -if [[ $? == 0 ]]; then - echo "Ok" -else - exit 1 -fi - -echo "Checking for common errors" -pyflakes ../mcwrapper -if [[ $? == 0 ]]; then - echo "Ok" -else - exit 1 -fi - -# This test is not part of standard check because of errors caused by dynamic variable -# loading to configuration. But it should be run from time to time to found other mistakes -#echo "Checking bugs and poor quality" -#pylint --reports=n ../mcwrapper -- cgit v1.2.3