package xenxier.minecraft.servermagic; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import xenxier.minecraft.servermagic.console.Console; import xenxier.minecraft.servermagic.console.command.LogCommand; import xenxier.minecraft.servermagic.event.Event; import xenxier.minecraft.servermagic.listener.Listener; import com.google.common.collect.Lists; public class Server implements Runnable { public final String server_name; public final int server_id; public final Minecraft minecraft; public final List<String> server_args; public final File server_dir; public final JSONObject server_json; public volatile InputStream server_log; private volatile OutputStream server_in; private volatile BufferedWriter server_writer; private volatile BufferedReader server_reader; private final ArrayList<Event> server_events = new ArrayList<Event>(); private final ArrayList<Listener> server_listeners = new ArrayList<Listener>(); private ProcessBuilder server_builder; private Process server_proc; public volatile Thread server_thread = new Thread(this); public Server(int id) { this.server_id = id; this.server_json = (JSONObject) Config.servers.get(id); this.server_name = server_json.get("name").toString(); this.minecraft = new Minecraft(server_json.get("minecraft").toString()); this.server_dir = new File(Reference.home_folder + File.separator + "servers" + File.separator + server_name); // Register server events: this.server_events.add(new xenxier.minecraft.servermagic.event.LoginEvent(this)); this.server_events.add(new xenxier.minecraft.servermagic.event.LogoutEvent(this)); // Register server listeners: this.server_listeners.add(new xenxier.minecraft.servermagic.listener.AboutListener(this)); // Register custom server listeners: JSONArray listener_json = (JSONArray) this.server_json.get("listeners"); if (!(listener_json == null)) { for (int i = 0; i < listener_json.size(); i++) { JSONObject current = (JSONObject) listener_json.get(i); this.server_listeners.add(new xenxier.minecraft.servermagic.listener.Listener(this, current.get("listen").toString(), current.get("execute").toString())); } } // Make sure our directory exists: if (!this.server_dir.exists()) { Logger.log("The server directory can't be found, creating it."); this.server_dir.mkdirs(); } // EULA try { flagEulaTrue(); } catch (IOException e1) { e1.printStackTrace(); } // Build the launch commands: server_args = Lists.newArrayList( Reference.java, "-jar", minecraft.getJarLocation().toString() ); // Add from config: if (Config.global != null && Config.global.get("arguments") != null) { server_args.add(Config.global.get("arguments").toString()); } if (!(server_json.get("arguments") == null)) { server_args.add(server_json.get("arguments").toString()); } try { overrideServerProperties(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } private void overrideServerProperties() throws IOException, InterruptedException { JSONObject propjson = (JSONObject) server_json.get("properties"); if (propjson == null || propjson.isEmpty()) { Logger.log("We don't have any overrides for this server in our JSON file, but that's okay.", this); return; } File f = new File(this.server_dir + File.separator + "server.properties"); if (!f.exists()) { /* * server.properties doesn't exist. * * To fix this without dumping all of our overrides into the server.properties file * or manually inserting default values into the file and overriding them, we are * going go create an instance of the server and wait until it creates the file, once * it does we're just going to ask it to stop. * */ Logger.log("Creating server to generate properties...", this); // Create the server: ProcessBuilder procbuild = new ProcessBuilder(this.server_args); procbuild.directory(this.server_dir); Process proc = procbuild.start(); // Wait until server has created server.properties (check every half second) while(!f.exists()){Thread.sleep(500);} // Kill the server and log that we got the properties file proc.destroy(); Logger.log("Server stopped. server.properties was created.", this); } MinecraftServerProperties propmc = new MinecraftServerProperties(f); // Loop through all properties in server.properties for (int i = 0; i < propmc.lines.size(); i++) { // If the property found exists in the JSON file: if (propjson.get(propmc.getPropOf(i)) != null) { propmc.modifyProp(propmc.getPropOf(i), propjson.get(propmc.getPropOf(i)).toString()); } } } private void flagEulaTrue() throws IOException { /* * Automatically flag eula=true inside eula.txt. * * > By executing this method you are agreeing to Mojang's EULA. * > https://account.mojang.com/documents/minecraft_eula */ BufferedWriter writer = new BufferedWriter(new FileWriter(this.server_dir + File.separator + "eula.txt")); writer.write("eula=true"); writer.close(); } @Override public synchronized void run() { Logger.log("Starting " + this.server_name); this.server_builder = new ProcessBuilder(this.server_args); this.server_builder.directory(this.server_dir); this.server_builder.redirectErrorStream(true); try { this.server_proc = this.server_builder.start(); this.server_in = this.server_proc.getOutputStream(); this.server_log = this.server_proc.getInputStream(); this.server_writer = new BufferedWriter(new OutputStreamWriter(server_in)); this.server_reader = new BufferedReader(new InputStreamReader(server_log)); /* * We have to do something with passCommand or Java will refuse to accept it as part * of our server process. * * A solution to this problem is to have an event on the server start, however having * an actual event class for something that can only happen once, and that doesn't have * a traditional trigger is difficult and would require a lot of changes and hacks in the * main event class. * * What we do below is parse what's under servers/<server>/events/start like an event, and * keep it in the events object, but we don't actually have an event class for it. * */ if (this.server_json.get("events") != null && ((JSONObject) this.server_json.get("events")).get("start") != null) { Event.parse(this,((JSONObject) this.server_json.get("events")).get("start").toString()); } else { passCommand("say ServerMagic started " + this.server_name); } if ((JSONObject) this.server_json.get("backup") != null) { // Backup objects: final Backup backup = new Backup(this); // Server Backup runnable: if (((JSONObject) this.server_json.get("backup")).get("server") != null) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); executor.scheduleAtFixedRate(new Runnable() { public void run() { try { backup.backupServer(); } catch (IOException e) { e.printStackTrace(); } } }, 0, (long) ((JSONObject) ((JSONObject) this.server_json.get("backup")).get("server")).get("time"), TimeUnit.MINUTES); } // World Backup runnable: if (((JSONObject) this.server_json.get("backup")).get("world") != null) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); executor.scheduleAtFixedRate(new Runnable() { public void run() { try { backup.backupWorld(); } catch (IOException e) { e.printStackTrace(); } } }, 0, (long) ((JSONObject) ((JSONObject) this.server_json.get("backup")).get("world")).get("time"), TimeUnit.MINUTES); } } // Server out: String line = null; StringBuilder out = new StringBuilder(); // The following code loops while the server is alive: while ((line = this.server_reader.readLine()) != null) { out.append(line); out.append(System.getProperty("line.separator")); // Logging logic: if (LogCommand.log != null && Console.current_server != null) { if ((LogCommand.log.equals("current") && Console.current_server == this) || LogCommand.log.equals("all")) { Logger.log(line.toString(), this); } } // Loop through events and listeners: for (int i = 0; i < this.server_events.size(); i++) { this.server_events.get(i).parseLine(line.toString()); } for (int i = 0; i < this.server_listeners.size(); i++) { this.server_listeners.get(i).parseLog(line.toString()); } } } catch (IOException e) { e.printStackTrace(); } } public void passCommand(String command) { try { this.server_writer.write(command.trim() + "\n"); this.server_writer.flush(); } catch (IOException e) { Logger.log("Error passing command. Maybe the server died?", this); } } }