Newer
Older
KeeperJerry_Launcher / LaunchServer / source / binary / JARLauncherBinary.java
package launchserver.binary;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import launcher.Launcher;
import launcher.LauncherAPI;
import launcher.helper.IOHelper;
import launcher.helper.LogHelper;
import launcher.helper.SecurityHelper;
import launcher.serialize.HOutput;
import launchserver.LaunchServer;

public final class JARLauncherBinary extends LauncherBinary {
	@LauncherAPI public static final Path RUNTIME_DIR = IOHelper.WORKING_DIR.resolve(Launcher.RUNTIME_DIR);
	@LauncherAPI public static final Path INIT_SCRIPT_FILE = RUNTIME_DIR.resolve(Launcher.INIT_SCRIPT_FILE);
	@LauncherAPI public static final Path JAR_BINARY_FILE = IOHelper.WORKING_DIR.resolve("Launcher.jar");

	@LauncherAPI
	public JARLauncherBinary(LaunchServer server) throws IOException {
		super(server, JAR_BINARY_FILE);
		tryUnpackRuntime();
	}

	@Override
	public void build() throws IOException {
		LogHelper.info("Building launcher binary file");
		try (JarOutputStream output = new JarOutputStream(IOHelper.newOutput(JAR_BINARY_FILE))) {
			output.setMethod(ZipOutputStream.DEFLATED);
			output.setLevel(Deflater.BEST_COMPRESSION);
			try (InputStream input = new GZIPInputStream(IOHelper.newInput(IOHelper.getResourceURL("Launcher.pack.gz")))) {
				Pack200.newUnpacker().unpack(input, output);
			}

			// Verify has init script file
			if (!IOHelper.isFile(INIT_SCRIPT_FILE)) {
				throw new IOException(String.format("Missing init script file ('%s')", Launcher.INIT_SCRIPT_FILE));
			}

			// Write launcher runtime dir
			Map<String, byte[]> runtime = new HashMap<>();
			IOHelper.walk(RUNTIME_DIR, new RuntimeDirVisitor(output, runtime), false);

			// Create launcher config file
			byte[] launcherConfigBytes;
			try (ByteArrayOutputStream configArray = IOHelper.newByteArrayOutput()) {
				try (HOutput configOutput = new HOutput(configArray)) {
					new Launcher.Config(server.config.getAddress(), server.config.port,
						server.publicKey, runtime).write(configOutput);
				}
				launcherConfigBytes = configArray.toByteArray();
			}

			// Write launcher config file
			output.putNextEntry(IOHelper.newZipEntry(Launcher.CONFIG_FILE));
			output.write(launcherConfigBytes);
		}
	}

	@LauncherAPI
	public void tryUnpackRuntime() throws IOException {
		// Verify is runtime dir unpacked
		if (IOHelper.isDir(RUNTIME_DIR)) {
			return; // Already unpacked
		}

		// Unpack launcher runtime files
		Files.createDirectory(RUNTIME_DIR);
		LogHelper.info("Unpacking launcher runtime files");
		try (ZipInputStream input = IOHelper.newZipInput(IOHelper.getResourceURL("launchserver/defaults/runtime.zip"))) {
			for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
				if (entry.isDirectory()) {
					continue; // Skip dirs
				}

				// Unpack runtime file
				IOHelper.transfer(input, RUNTIME_DIR.resolve(IOHelper.toPath(entry.getName())));
			}
		}
	}

	private static final class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
		private final ZipOutputStream output;
		private final Map<String, byte[]> runtime;

		private RuntimeDirVisitor(ZipOutputStream output, Map<String, byte[]> runtime) {
			this.output = output;
			this.runtime = runtime;
		}

		@Override
		public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
			String dirName = IOHelper.toString(RUNTIME_DIR.relativize(dir));
			output.putNextEntry(newEntry(dirName + '/'));
			return super.preVisitDirectory(dir, attrs);
		}

		@Override
		public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
			String fileName = IOHelper.toString(RUNTIME_DIR.relativize(file));
			runtime.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file));

			// Create zip entry and transfer contents
			output.putNextEntry(newEntry(fileName));
			IOHelper.transfer(file, output);

			// Return result
			return super.visitFile(file, attrs);
		}

		private static ZipEntry newEntry(String fileName) {
			return IOHelper.newZipEntry(Launcher.RUNTIME_DIR + IOHelper.CROSS_SEPARATOR + fileName);
		}
	}
}