aboutsummaryrefslogtreecommitdiff
path: root/mcwrapper/wrapper.py
blob: 96f74d5ff088cf62daca308c58f426e4bda30c3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# 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

__INPUTPIPE__ = 'input_pipe'
__PIDFILE__ = 'server.pid'


class MCWrapper:
    "Minecraft server wrapper class"
    def __init__(self, command):
        self.process = None
        self.command = command
        self._running = False
        self._hook_start = []
        self._hook_stop = []
        self._hook_line = []
        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
        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

    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)
        for h in self._hook_start:
            h()
        self._running = True
        with open(__PIDFILE__, "w") as file:
            file.write(str(self.process.pid))
        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()
        self._running = False

    def running(self):
        "Returns True if mc server is running. Othervise False."
        return True

    def write_to_terminal(self, text):
        "Write to server terminal. If server not running it does nothing"
        if self._running:
            prints.info("Input: " + text.rstrip(), 1)
            self.process.stdin.write(bytes(text, sys.getdefaultencoding()))
            self.process.stdin.flush()
            return True
        else:
            return False

    def hook_start(self, handler):
        self._hook_start.append(handler)

    def hook_stop(self, handler):
        self._hook_stop.append(handler)

    def hook_line(self, contains, handler):
        n = dict()
        n["contains"] = contains
        n["handler"] = handler
        self._hook_line.append(n)

    def __parse_line__(self, line):
        i = 0
        while i < len(self._hook_line):
            if self._hook_line[i]["contains"] in line:
                self._hook_line[i]["handler"](line)
            i += 1

    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())

    def __input_thread__(self):
        with open(__INPUTPIPE__, 'r') as pipe:
            while True:
                line = pipe.readline().rstrip()
                # TODO use polling
                if line:
                    self.write_to_terminal(line + "\n")
                else:
                    time.sleep(3)