-1

I feel like this must be some kind of scoping issue but after a lot of research I'm still stumped. I'm building a small minecraft server wrapper and it's currently working with the following code (full source on github: Simple Minecraft Server Wrapper - app.py):

mc = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, cwd=results.path)
    time.sleep(5)
    while run == 1:
        print '--- Checking for new versions in ' + str(check_for_new_versions_frequency) + ' seconds.'
        time.sleep(check_for_new_versions_frequency)
        print '--- Checking for a new version...'
        mc.stdin.write('say SMSW - Checking for new version...\n')
        mc.stdin.flush()
        if up_to_date(current_ver) == False:
            mc.stdin.write('say SMSW - New version detected, rebooting for update in 30 seconds...\n')
            mc.stdin.flush()

This will start the server and check for new updates at a predetermined interval. This also will message within the game to alert connected players that it is checking for updates and warn them of a reboot if an update is needed. This is working without any issues.

I wanted to clean up the code a bit and move messaging, startup, and shutdown to a separate class called ServerManager. The class works to start the server but messaging does not work. Here's that class:

import time, subprocess

class ServerManager:

    def __init__(self, path, server_file, xms=1, xmx=1, gui=False):
        self.path = path
        self.server_file = server_file
        self.online = False
        self.xms = xms
        self.xmx = xmx
        self.gui = gui

    def start(self):
        global process
        command = 'java -jar -Xms' + str(self.xms) + 'G -Xmx' + str(self.xmx) + 'G ' + self.path + self.server_file
        if self.gui == False: command += ' nogui'
        print command
        if self.path !='':self.process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, cwd=self.path)
        if self.path =='':self.process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE)
        print self.server_process
        self.online = True

    def shutdown(self):
        self.message('New version detected, server will be shutting down for maintenance in 1 minute.')
        time.sleep(30)
        self.message('Shutting down for maintenance in 30 seconds.')
        time.sleep(20)
        self.message('Shutting down for maintenance in 10 seconds.')
        self.message('Have some diamonds for the trouble.')
        self.message('give @a minecraft:diamond 5', true)
        self.online = False
        self.process.terminate()
        del process

    #Server chat can be passed as ServerManager.message('hello')
    def message(self, message, command=False):
        print self.server_process
        if command == False:
            self.process.stdin.write('### SMSW Message: ')
            self.process.stdin.flush()
            self.process.stdin.write(message)
            self.process.stdin.flush()
        else:
            self.process.stdin.write(message)
            self.process.stdin.flush()

The strangest part is when I modify my application to utilize this class, at the points where the server messaging should occur I do not receive any errors. It just doesn't actually do the messaging. I've done a print(process) at the startup and messaging functions at get the same object reference so it seems like it should work. Server shutdown also does not work, I'm guessing it's the same issue as the 'server' object appears to be present but inaccessible for some reason.

Here's the modified main app code that is utilizing the class:

def main():
process_args()
server = ServerManager(results.path, mc_server, 1, 1)
global current_ver
global run
print '*' * 40
print '* Simple Minecraft Server Wrapper'
print '*' * 40
latest_ver = str(get_version())
if current_ver != latest_ver:
    download_server(latest_ver)
    current_ver = latest_ver
if not server.online:
    server.start()
    time.sleep(5)
    while server.online:
        print '--- Checking for new versions in ' + str(check_for_new_versions_frequency) + ' seconds.'
        time.sleep(check_for_new_versions_frequency)
        print '--- Checking for a new version...'
        server.message('Checking for a new version...')
        # Checking for new version
        if not up_to_date(current_ver):
            # new version detected
            time.sleep(30)
            server.online = False
            time.sleep(5)
            print '--- Server stopped'
            main()

Output from this is:

****************************************
* Simple Minecraft Server Wrapper
****************************************
--- The latest version of Minecraft is 1.8.8
--- Downloading 1.8.8
--- Download complete.
--- Started server with command: java -jar -Xms1G -Xmx1G minecraft_server.jar no
gui
<subprocess.Popen object at 0x029BBDB0>
[10:54:44] [Server thread/INFO]: Starting minecraft server version 1.8.8
[10:54:44] [Server thread/INFO]: Loading properties
[10:54:44] [Server thread/INFO]: Default game type: SURVIVAL
[10:54:44] [Server thread/INFO]: Generating keypair
[10:54:44] [Server thread/INFO]: Starting Minecraft server on *:25565
[10:54:44] [Server thread/INFO]: Using default channel type
[10:54:44] [Server thread/INFO]: Preparing level "world"
[10:54:44] [Server thread/INFO]: Preparing start region for level 0
[10:54:45] [Server thread/INFO]: Done (1.092s)! For help, type "help" or "?"
--- Checking for new versions in 20 seconds.
--- Checking for a new version...
<subprocess.Popen object at 0x029BBDB0>
--- The latest version of Minecraft is 1.8.8
--- Up to date.
--- Checking for new versions in 20 seconds.
--- Checking for a new version...
<subprocess.Popen object at 0x029BBDB0>

The first subprocess object print is from the start function in the class, the second is from the message function. The references match like you would expect but the stdout.write does not work.

Any ideas?

dyonak
  • 23
  • 4
  • You should show how you are using this class. Do you ever call the functions? Also note that you are doing a lot of strange things; you have a global variable which you never use, and you unnecessarily use `return` where it is not needed. – Daniel Roseman Aug 29 '15 at 15:34
  • Thanks Daniel, I've added the modified app.py code that utilizes the class. I also removed the extraneous returns from the class functions. – dyonak Aug 29 '15 at 15:52
  • Forget that `global` exists in the language. I have no idea if it is helping to screw things up, I just know that it often does. – msw Aug 29 '15 at 16:39

2 Answers2

1

You are sending different commands to your process. In the first script you send 'say SMSW - Checking for new version...\n' in the second one 'Checking for a new version...'.

Daniel
  • 42,087
  • 4
  • 55
  • 81
  • thank you! It was going to stdin but missing the newline at the end which caused it to never actually be recognized as an input to the server. – dyonak Aug 29 '15 at 16:48
0

It may be the way you are using subprocess.Popen in the refactored code. Specifically, the command argument should be a list, not a string.

See https://stackoverflow.com/a/12605520/2653356

PS. I've personally given up on subprocess and mostly use the commands module.

Community
  • 1
  • 1