package net.classicube.selfupdater; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import javax.swing.JOptionPane; import net.classicube.shared.SharedUpdaterCode; public class Program { private static final Logger logger = Logger.getLogger(Program.class.getName()); private static final String LAUNCHER_ENTRY_CLASS = "net.classicube.launcher.EntryPoint", LAUNCHER_JAR_NAME = "launcher.jar", LAUNCHER_ENTRY_METHOD = "main", BUG_REPORT_URL = "http://is.gd/CCL_bugs"; private static File launcherDir, launcherJar; public static void main(String[] args) { System.setProperty("java.net.preferIPv4Stack", "true"); try { launcherDir = SharedUpdaterCode.getLauncherDir(); } catch (IOException ex) { fatalError("Error finding launcher's directory.", ex); } launcherJar = new File(launcherDir, LAUNCHER_JAR_NAME); final File newLauncherJar = new File(launcherDir, SharedUpdaterCode.LAUNCHER_NEW_JAR_NAME); initLogging(); while (true) { try { if (newLauncherJar.exists()) { replaceFile(newLauncherJar, launcherJar); } else if (!launcherJar.exists()) { ProgressIndicator progressWindow = new ProgressIndicator(); progressWindow.setVisible(true); downloadLauncher(); progressWindow.dispose(); } SharedUpdaterCode.testLzma(logger); startLauncher(launcherJar); return; } catch (final Exception ex) { logger.log(Level.SEVERE, "Failed to start launcher", ex); final String message = String.format( "<html>Could not start the ClassiCube launcher:" + "<blockquote><i>%s</i></blockquote>" + "If clicking [Retry] does not help, please report this problem at %s", new Object[]{exceptionToString(ex), BUG_REPORT_URL}); Object[] options = {"Abort", "Retry"}; int chosenOption = JOptionPane.showOptionDialog(null, message, "ClassiCube Launcher Error", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[1]); if (chosenOption != 1) { // Abort System.exit(1); } else { // Retry: delete launcher files and re-download deleteLauncherFiles(); } } } } private static void initLogging() { logger.setLevel(Level.ALL); final File logFile = new File(launcherDir, "selfupdater.log"); try { final FileHandler handler = new FileHandler(logFile.getAbsolutePath()); handler.setFormatter(new SimpleFormatter()); logger.addHandler(handler); } catch (final IOException | SecurityException ex) { fatalError("Could not create log file:", ex); } } private static void downloadLauncher() throws IOException { final File lzmaJar = new File(launcherDir, SharedUpdaterCode.LZMA_JAR_NAME); if (!lzmaJar.exists()) { final File lzmaTempFile = downloadFile("lzma.jar"); replaceFile(lzmaTempFile, lzmaJar); } final File launcherTempFile = downloadFile("launcher.jar.pack.lzma"); try { final File processedLauncherFile = SharedUpdaterCode.processDownload( logger, launcherTempFile, "launcher.jar.pack.lzma", "launcher.jar"); replaceFile(processedLauncherFile, launcherJar); } catch (IOException ex) { logger.log(Level.SEVERE, "Error unpacking launcher.jar", ex); throw new IOException("Error unpacking launcher.jar", ex); } } private static void startLauncher(final File launcherJar) throws Exception { final Class<?> lpClass = loadLauncher(launcherJar); final Method entryPoint = lpClass.getMethod(LAUNCHER_ENTRY_METHOD, String[].class); entryPoint.invoke(null, (Object) new String[0]); } // Load the entry point from launcher's jar private static Class<?> loadLauncher(final File launcherJar) throws IOException, ClassNotFoundException { final URL[] urls = {new URL("jar:file:" + launcherJar + "!/")}; final URLClassLoader loader = URLClassLoader.newInstance(urls); return loader.loadClass(LAUNCHER_ENTRY_CLASS); } private static File downloadFile(final String remoteName) throws IOException { try { final File tempFile = File.createTempFile(remoteName, ".downloaded"); final URL website = new URL(SharedUpdaterCode.BASE_URL + remoteName); final ReadableByteChannel rbc = Channels.newChannel(website.openStream()); try (final FileOutputStream fos = new FileOutputStream(tempFile)) { fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); } return tempFile; } catch (IOException ex) { logger.log(Level.SEVERE, "Error downloading launcher component " + remoteName, ex); throw new IOException("Error downloading launcher component " + remoteName, ex); } } // Replace contents of destFile with sourceFile private static void replaceFile(final File sourceFile, final File destFile) throws IOException { try { destFile.createNewFile(); try (final FileChannel source = new FileInputStream(sourceFile).getChannel()) { try (final FileChannel destination = new FileOutputStream(destFile).getChannel()) { destination.transferFrom(source, 0, source.size()); } } sourceFile.delete(); } catch (final IOException ex) { logger.log(Level.SEVERE, "Error deploying launcher component: " + destFile.getName(), ex); throw new IOException("Error deploying launcher component: " + destFile.getName(), ex); } } // Attempts to delete all files (except log files) inside launcher's directory. // Exits program via fatalError(...) on error. private static void deleteLauncherFiles() { try { final File[] files = launcherDir.listFiles(); if (files == null) { // null if security restricted throw new IOException("Failed to list contents of " + launcherDir); } for (final File file : files) { if (!file.isDirectory() && !file.getName().toLowerCase().endsWith(".log")) { if (!file.delete()) { logger.log(Level.WARNING, "Unable to delete {0}", file.getName()); } } } } catch (IOException ex) { logger.log(Level.SEVERE, "Error deleting launcher files.", ex); fatalError("Unable to recover from an earlier error:", ex); } } private static void fatalError(String message, Throwable ex) { String htmlMessage; if (ex != null) { htmlMessage = String.format( "<html>%s<blockquote><i>%s</i></blockquote>If this problem persists, contact us at %s", new Object[]{message, exceptionToString(ex), BUG_REPORT_URL}); } else { htmlMessage = String.format( "<html>%s<br>If this problem persists, contact us at <u>%s</u>", new Object[]{message, BUG_REPORT_URL}); } JOptionPane.showMessageDialog(null, htmlMessage, "ClassiCube Launcher Error", JOptionPane.ERROR_MESSAGE); System.exit(1); } private static String exceptionToString(Throwable ex) { StringBuilder sb = new StringBuilder(); do { if (sb.length() > 0) { sb.append("<br>caused by "); } StackTraceElement frame = ex.getStackTrace()[0]; sb.append(ex) .append("<br>    at ") .append(frame.getClassName()).append('.').append(frame.getMethodName()); ex = ex.getCause(); } while (ex != null); return sb.toString(); } }