/src/main/java/org/spoutcraft/launcher/UpdateThread.java
Java | 637 lines | 499 code | 70 blank | 68 comment | 125 complexity | e596938d8ed0074e497b079f5ec7ba20 MD5 | raw file
- /*
- * This file is part of Westeroscraft Launcher.
- *
- * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
- * Westeroscraft Launcher is licensed under the Spout License Version 1.
- *
- * Westeroscraft Launcher is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * In addition, 180 days after any changes are published, you can use the
- * software, incorporating those changes, under the terms of the MIT license,
- * as described in the Spout License Version 1.
- *
- * Westeroscraft Launcher is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License,
- * the MIT license and the Spout License Version 1 along with this program.
- * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
- * License and see <http://spout.in/licensev1> for the full license,
- * including the MIT license.
- */
- /*
- * This file is part of Spoutcraft Launcher.
- *
- * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
- * Spoutcraft Launcher is licensed under the Spout License Version 1.
- *
- * Spoutcraft Launcher is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * In addition, 180 days after any changes are published, you can use the
- * software, incorporating those changes, under the terms of the MIT license,
- * as described in the Spout License Version 1.
- *
- * Spoutcraft Launcher is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License,
- * the MIT license and the Spout License Version 1 along with this program.
- * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
- * License and see <http://spout.in/licensev1> for the full license,
- * including the MIT license.
- */
- package org.spoutcraft.launcher;
- import java.io.File;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Calendar;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.concurrent.atomic.AtomicBoolean;
- import java.util.concurrent.atomic.AtomicReference;
- import java.util.jar.JarFile;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import org.spoutcraft.launcher.api.Launcher;
- import org.spoutcraft.launcher.api.SpoutcraftDirectories;
- import org.spoutcraft.launcher.exceptions.RestfulAPIException;
- import org.spoutcraft.launcher.exceptions.UnsupportedOSException;
- import org.spoutcraft.launcher.launch.MinecraftClassLoader;
- import org.spoutcraft.launcher.launch.MinecraftLauncher;
- import org.spoutcraft.launcher.rest.Library;
- import org.spoutcraft.launcher.rest.Minecraft;
- import org.spoutcraft.launcher.rest.RestAPI;
- import org.spoutcraft.launcher.rest.Versions;
- import org.spoutcraft.launcher.util.Download;
- import org.spoutcraft.launcher.util.DownloadListener;
- import org.spoutcraft.launcher.util.DownloadUtils;
- import org.spoutcraft.launcher.util.FileUtils;
- import org.spoutcraft.launcher.util.MD5Utils;
- import org.spoutcraft.launcher.util.OperatingSystem;
- import org.spoutcraft.launcher.util.Utils;
- import org.spoutcraft.launcher.util.Download.Result;
- import org.spoutcraft.launcher.yml.Resources;
- import org.spoutcraft.launcher.yml.YAMLProcessor;
- public class UpdateThread extends Thread {
- /**
- * We only want to clean the old logs, temp folders once per startup
- */
- private static final AtomicBoolean cleaned = new AtomicBoolean(false);
- private static final int PRELOAD_CLASSES = 100;
- public static final String[] OLD_ASSETS = { "1.2.3", "1.2.5", "1.3.1", "1.3.2", "1.4.2", "1.4.4", "1.4.5", "1.4.6", "1.4.7", "1.5" };
- private final Logger logger = Logger.getLogger("launcher");
- private final AtomicBoolean waiting = new AtomicBoolean(false);
- private final AtomicBoolean valid = new AtomicBoolean(false);
- private final AtomicBoolean finished = new AtomicBoolean(false);
- private final StartupParameters params = Utils.getStartupParameters();
- private final DownloadListener listener = new DownloadListenerWrapper();
- private volatile SpoutcraftData build;
- private final Thread previous;
- public UpdateThread() {
- this(null);
- }
- public UpdateThread(Thread previous) {
- super("Update Thread");
- setDaemon(true);
- this.previous = previous;
- }
- public SpoutcraftData getBuild() {
- return build;
- }
- @Override
- public void run() {
- try {
- build = new SpoutcraftData();
- boolean interrupted = false;
- while(true) {
- try {
- if (previous != null) {
- previous.join();
- }
- break;
- } catch (InterruptedException e) {
- interrupted = true;
- }
- }
- if (!interrupted) {
- runTasks();
- }
- } catch (Exception e) {
- if (!this.isInterrupted()) {
- Launcher.getLoginFrame().handleException(e);
- }
- }
- }
- private void runTasks() throws IOException {
- while (!valid.get()) {
- boolean minecraftUpdate = isMinecraftUpdateAvailable(build);
- boolean spoutcraftUpdate = minecraftUpdate || isSpoutcraftUpdateAvailable(build);
- if (minecraftUpdate) {
- updateMinecraft(build);
- }
- if (spoutcraftUpdate) {
- updateSpoutcraft(build);
- }
- updateAssets();
- // Download assets
- if (cleaned.compareAndSet(false, true)) {
- Resources.VIP.getYAML();
- Resources.Special.getYAML();
- Versions.getMinecraftVersions();
- cleanLogs();
- cleanTemp();
- updateFiles();
- RestAPI.getCache().cleanup();
- }
- Validator validate = new Validator();
- if (!(params.isIgnoreMD5() || Settings.isIgnoreMD5())) {
- validate.run(build);
- valid.set(validate.isValid());
- } else {
- valid.set(true);
- }
- }
- MinecraftClassLoader loader;
- loader = MinecraftLauncher.getClassLoader(build.getLibraries());
- int loaded = 0;
- while (!waiting.get()) {
- int pass = loader.preloadClasses(PRELOAD_CLASSES);
- loaded += pass;
- // Less than the preload amount, so we are finished
- if (pass != PRELOAD_CLASSES) {
- break;
- }
- }
- logger.info("Preloaded " + loaded + " classes in advance");
- finished.set(true);
- }
- private void updateAssets() {
- YAMLProcessor assets = Resources.Assets.getYAML();
- if (assets != null) {
- updateAssets(assets.getMap(), Utils.getAssetsDirectory());
- }
- if(Settings.getUseLatestTexturepack()) {
- YAMLProcessor textures = Resources.Texturepacks.getYAML();
- if (textures != null) {
- updateAssets(textures.getMap(), Utils.getTexturePackDirectory());
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- private void updateAssets(Map<String, Object> assets, File directory) {
- directory.mkdirs();
- for (Entry<String, Object> entry : assets.entrySet()) {
- String key = entry.getKey();
- Object value = entry.getValue();
- if (value instanceof Map<?, ?>) {
- updateAssets((Map<String, Object>)value, new File(directory, key));
- } else if (value instanceof String) {
- String url = (String)value;
- String name = getFileName(url);
- File asset = new File(directory, name);
- stateChanged("Verifying Asset: " + name, 0);
- boolean needDownload = true;
- if (asset.exists() && !(params.isIgnoreMD5() || Settings.isIgnoreMD5())) {
- String md5 = MD5Utils.getMD5(asset);
- Launcher.debug("Checking MD5 of " + asset.getName() + ". Expected MD5: " + key + " | Actual MD5: " + md5);
- needDownload = md5 == null || !md5.equals(key);
- } else if (asset.exists() && (params.isIgnoreMD5() || Settings.isIgnoreMD5())) {
- needDownload = false;
- }
- if (needDownload) {
- try {
- DownloadUtils.downloadFile(url, asset.getPath(), null, key, listener);
- } catch (IOException e) {
- logger.log(Level.SEVERE, "Failed to download asset [" + url + "]", e);
- }
- }
- stateChanged("Verified Asset: " + name, 100);
- } else {
- logger.warning("Unknown asset type for " + key + ". Type is " + value);
- }
- }
- }
- private String getFileName(String url) {
- String[] split = url.split("/");
- return split[split.length - 1];
- }
- private void updateFiles() {
- SpoutcraftDirectories dirs = new SpoutcraftDirectories();
- File oldConfig = new File(Utils.getWorkingDirectory(), "westeroscraft");
- if (oldConfig.exists() && oldConfig.isDirectory()) {
- moveDirectory(oldConfig, dirs.getSpoutcraftDir());
- FileUtils.deleteQuietly(oldConfig);
- }
- }
- private void moveDirectory(File dir, File newDir) {
- if (!dir.exists()) {
- dir.mkdirs();
- }
- for (File file : dir.listFiles()) {
- if (file.isDirectory()) {
- moveDirectory(file, new File(newDir, file.getName()));
- } else {
- file.renameTo(new File(newDir, file.getName()));
- }
- }
- }
- private void cleanTemp() {
- SpoutcraftDirectories dirs = new SpoutcraftDirectories();
- File binDir = dirs.getBinDir();
- for (File f : binDir.listFiles()) {
- if (f.isDirectory() && f.getName().startsWith("temp_")) {
- FileUtils.deleteQuietly(f);
- }
- }
- }
- private void cleanLogs() {
- File logDirectory = new File(Utils.getWorkingDirectory(), "logs");
- if (logDirectory.exists() && logDirectory.isDirectory()) {
- for (File log : logDirectory.listFiles()) {
- if (!log.getName().endsWith(".log")) {
- log.delete();
- continue;
- }
- if (!log.getName().startsWith("westeroscraft")) {
- log.delete();
- continue;
- }
- String[] split = log.getName().split("_");
- if (split.length != 2) {
- log.delete();
- continue;
- }
- String[] date = split[1].split("-");
- if (date.length != 3) {
- log.delete();
- continue;
- }
- date[2] = date[2].substring(0, date[2].length() - 4); // Trim .log extension
- try {
- int logYear = Integer.parseInt(date[0]);
- int logMonth = Integer.parseInt(date[1]);
- int logDay = Integer.parseInt(date[2]);
- Calendar logDate = Calendar.getInstance();
- // Add a month to the calendar (clear logs older than 1 month)
- if (logMonth < 12) {
- logMonth++;
- } else {
- logMonth = 1;
- logYear++;
- }
- logDate.set(logYear, logMonth, logDay);
- if (Calendar.getInstance().after(logDate)) {
- log.delete();
- }
- } catch (NumberFormatException ignore) {
- log.delete();
- continue;
- }
- }
- }
- }
- public void setWaiting(boolean waiting) {
- this.waiting.set(waiting);
- }
- public boolean isFinished() {
- return finished.get();
- }
- public boolean isValidInstall() {
- return valid.get();
- }
- public boolean isSpoutcraftUpdateAvailable(SpoutcraftData build) throws RestfulAPIException {
- if (!Utils.getWorkingDirectory().exists()) {
- return true;
- }
- if (!Launcher.getGameUpdater().getSpoutcraftDir().exists()) {
- return true;
- }
- List<Library> libraries = build.getLibraries();
- int steps = libraries.size() + 2;
- float progress = 100F;
- stateChanged("Checking for Westeroscraft update...", progress / steps);
- progress += 100F;
- File spoutcraft = new File(Launcher.getGameUpdater().getBinDir(), "westeroscraft.jar");
- if (!spoutcraft.exists()) {
- return true;
- }
- if (!Settings.isIgnoreMD5() && !build.getMD5().equalsIgnoreCase(MD5Utils.getMD5(spoutcraft))) {
- return true;
- }
- stateChanged("Checking for Westeroscraft update...", progress / steps);
- progress += 100F;
- File libDir = new File(Launcher.getGameUpdater().getBinDir(), "lib");
- libDir.mkdir();
- for (Library lib : libraries) {
- File libraryFile = new File(libDir, lib.name() + ".jar");
- if (!libraryFile.exists()) {
- return true;
- }
- stateChanged("Checking for Westeroscraft update...", progress / steps);
- progress += 100F;
- }
- return false;
- }
- public boolean isMinecraftUpdateAvailable(SpoutcraftData build) {
- int steps = 7;
- if (!Launcher.getGameUpdater().getBinDir().exists()) {
- return true;
- }
- stateChanged("Checking for Minecraft update...", 100F / steps);
- File nativesDir = new File(Launcher.getGameUpdater().getBinDir(), "natives");
- if (!nativesDir.exists()) {
- return true;
- }
- // Empty directory
- if (nativesDir.listFiles().length == 0) {
- return true;
- }
- stateChanged("Checking for Minecraft update...", 200F / steps);
- File minecraft = new File(Launcher.getGameUpdater().getBinDir(), "minecraft.jar");
- if (!minecraft.exists()) {
- return true;
- }
- stateChanged("Checking for Minecraft update...", 300F / steps);
- File lib = new File(Launcher.getGameUpdater().getBinDir(), "jinput.jar");
- if (!lib.exists()) {
- return true;
- }
- stateChanged("Checking for Minecraft update...", 400F / steps);
- lib = new File(Launcher.getGameUpdater().getBinDir(), "lwjgl.jar");
- if (!lib.exists()) {
- return true;
- }
- stateChanged("Checking for Minecraft update...", 500F / steps);
- lib = new File(Launcher.getGameUpdater().getBinDir(), "lwjgl_util.jar");
- if (!lib.exists()) {
- return true;
- }
- stateChanged("Checking for Minecraft update...", 600F / steps);
- String installed = Settings.getInstalledMC();
- stateChanged("Checking for Minecraft update...", 700F / steps);
- String required = build.getMinecraft().getVersion();
- return installed == null || !installed.equals(required);
- }
- // Searches the Minecraft libraries for the one matching the given artifact ID, returns the MD5
- protected static String findMd5(String artifactId, OperatingSystem platform, List<Library> libraries) {
- for (Library lib : libraries) {
- if (lib.getArtifactId().equalsIgnoreCase(artifactId)) {
- if (platform == null
- || platform.isWindows() && lib.getVersion().toLowerCase().endsWith("windows")
- || platform.isMac() && lib.getVersion().toLowerCase().endsWith("osx")
- || platform.isUnix() && lib.getVersion().toLowerCase().endsWith("linux")) {
- return lib.getMd5();
- }
- }
- }
- // Should never get here...
- throw new IllegalArgumentException("No valid library for " + artifactId + ", with platform " + platform + " was found");
- }
- public void updateMinecraft(SpoutcraftData build) throws IOException {
- Launcher.getGameUpdater().getBinDir().mkdir();
- Launcher.getGameUpdater().getBinCacheDir().mkdir();
- if (Launcher.getGameUpdater().getUpdateDir().exists()) {
- FileUtils.deleteDirectory(Launcher.getGameUpdater().getUpdateDir());
- }
- Launcher.getGameUpdater().getUpdateDir().mkdir();
- final Minecraft minecraft = build.getMinecraft();
- final String minecraftMD5 = minecraft.getMd5();
- final String jinputMD5 = findMd5("jinput", null, minecraft.getLibraries());
- final String lwjgl_utilMD5 = findMd5("lwjgl_util", null, minecraft.getLibraries());
- final String lwjglMD5 = findMd5("lwjgl", null, minecraft.getLibraries());
- // Process minecraft.jar
- logger.info("Minecraft Version: " + build.getMinecraft());
- File mcCache = new File(Launcher.getGameUpdater().getBinCacheDir(), "minecraft_" + minecraft.getVersion() + ".jar");
- if (!mcCache.exists() || (minecraftMD5 == null || !minecraftMD5.equals(MD5Utils.getMD5(mcCache)))) {
- if (Arrays.asList(OLD_ASSETS).contains(minecraft.getVersion())) {
- DownloadUtils.downloadFile("http://assets.minecraft.net/" + minecraft.getVersion().replaceAll("\\.", "_") + "/minecraft.jar", mcCache.getPath(), null, minecraftMD5, listener);
- } else {
- DownloadUtils.downloadFile("http://s3.amazonaws.com/Minecraft.Download/versions/" + minecraft.getVersion() + "/" + minecraft.getVersion() + ".jar", mcCache.getPath(), null, minecraftMD5, listener);
- }
- }
- Utils.copy(mcCache, new File(Launcher.getGameUpdater().getBinDir(), "minecraft.jar"));
- File nativesDir = new File(Launcher.getGameUpdater().getBinDir(), "natives");
- nativesDir.mkdir();
- stateChanged("Extracting Files...", 0);
- Settings.setInstalledMC(build.getMinecraft().getVersion());
- Settings.getYAML().save();
- }
- public void updateSpoutcraft(SpoutcraftData build) throws IOException {
- cleanupBinFoldersFor(build);
- Launcher.getGameUpdater().getUpdateDir().mkdirs();
- Launcher.getGameUpdater().getBinCacheDir().mkdirs();
- Launcher.getGameUpdater().getSpoutcraftDir().mkdirs();
- File cacheDir = new File(Launcher.getGameUpdater().getBinDir(), "cache");
- cacheDir.mkdir();
- // Process spoutcraft.jar
- logger.info("Westeroscraft Build: " + build.getBuild());
- File mcCache = new File(Launcher.getGameUpdater().getBinCacheDir(), "minecraft_" + build.getMinecraft().getVersion() + ".jar");
- File updateMC = new File(Launcher.getGameUpdater().getUpdateDir().getPath() + File.separator + "minecraft.jar");
- if (mcCache.exists()) {
- Utils.copy(mcCache, updateMC);
- }
- File spoutcraft = new File(Launcher.getGameUpdater().getBinDir(), "westeroscraft.jar");
- if (spoutcraft.exists()) {
- File spoutcraftCache;
- if (Integer.parseInt(build.getInstalledBuild()) > 0) {
- // Save our installed copy
- spoutcraftCache = new File(cacheDir, "westeroscraft_" + build.getInstalledBuild() + ".jar");
- if (!spoutcraftCache.exists()) {
- Utils.copy(spoutcraft, spoutcraftCache);
- }
- }
- spoutcraft.delete();
- // Check for an old copy of this build if it is already saved
- spoutcraftCache = new File(cacheDir, "westeroscraft_" + build.getBuild() + ".jar");
- if (spoutcraftCache.exists()) {
- Utils.copy(spoutcraftCache, spoutcraft);
- }
- }
- stateChanged("Looking Up Mirrors...", 0F);
- String url = build.getSpoutcraftURL();
- if (!spoutcraft.exists()) {
- Download download = DownloadUtils.downloadFile(url, Launcher.getGameUpdater().getUpdateDir() + File.separator + "westeroscraft.jar", null, build.getMD5(), listener);
- if (download.getResult() == Result.SUCCESS) {
- Utils.copy(download.getOutFile(), spoutcraft);
- }
- }
- File libDir = new File(Launcher.getGameUpdater().getBinDir(), "lib");
- libDir.mkdir();
- List<Library> allLibraries = new ArrayList<Library>();
- allLibraries.addAll(build.getLibraries());
- allLibraries.addAll(build.getMinecraft().getLibraries());
- for (Library lib : allLibraries) {
- File libraryFile = new File(libDir, lib.name() + ".jar");
- if (lib.getArtifactId().contains("lwjgl") || lib.getArtifactId().contains("jinput")) {
- if (lib.getVersion().contains("natives")) {
- File nativesFolder = new File(Launcher.getGameUpdater().getBinDir(), "natives");
- //This prevents downloading the wrong natives jar
- if (lib.getVersion().contains("osx") && !OperatingSystem.getOS().isMac()) {
- continue;
- } else if (lib.getVersion().contains("linux") && !OperatingSystem.getOS().isUnix()) {
- continue;
- } else if (lib.getVersion().contains("windows") && !OperatingSystem.getOS().isWindows()) {
- continue;
- }
- // Download natives
- File nativesJar = new File(Launcher.getGameUpdater().getUpdateDir(), "natives.jar");
- if (!nativesJar.exists() || !lib.valid(MD5Utils.getMD5(nativesJar))) {
- lib.download(nativesJar, listener);
- // Extract natives into the proper directory
- List<String> ignores = new ArrayList<String>();
- ignores.add("META-INF");
- File tempNatives = new File(Launcher.getGameUpdater().getUpdateDir(), "natives");
- Utils.extractJar(new JarFile(nativesJar), tempNatives, ignores);
- FileUtils.moveDirectory(tempNatives, nativesFolder);
- }
- continue;
- }
- libraryFile = new File(Launcher.getGameUpdater().getBinDir(), lib.getArtifactId() + ".jar");
- }
- if (libraryFile.exists()) {
- String computedMD5 = MD5Utils.getMD5(libraryFile);
- if (!lib.valid(computedMD5)) {
- logger.warning("MD5 check of " + libraryFile.getName() + " failed. Deleting and Redownloading.");
- libraryFile.delete();
- }
- }
- File cachedLibraryFile = new File(cacheDir, lib.name() + ".jar");
- if (cachedLibraryFile.exists()) {
- String computedMD5 = MD5Utils.getMD5(cachedLibraryFile);
- if (lib.valid(computedMD5)) {
- Utils.copy(cachedLibraryFile, libraryFile);
- }
- }
- if (!libraryFile.exists()) {
- lib.download(libraryFile, listener);
- }
- }
- }
- public void cleanupBinFoldersFor(SpoutcraftData build) {
- try {
- if (!Launcher.getGameUpdater().getBinDir().exists()) {
- return;
- }
- HashSet<String> neededBinFiles = new HashSet<String>(Arrays.asList(new String[]{"westeroscraft.jar", "minecraft.jar", "lwjgl.jar", "lwjgl_util.jar", "jinput.jar"}));
- for (File file : Launcher.getGameUpdater().getBinDir().listFiles()) {
- if (!file.isFile()) {
- continue;
- }
- if (neededBinFiles.contains(file.getName())) {
- continue;
- }
- file.delete();
- }
- } catch (Exception e) {
- System.err.println("Error while cleaning files: ");
- e.printStackTrace();
- }
- }
- public DownloadListener getDownloadListener() {
- return listener;
- }
- public void setDownloadListener(DownloadListener listener) {
- ((DownloadListenerWrapper)this.listener).setDownloadListener(listener);
- }
- public void stateChanged(String message, float progress) {
- if (listener != null) {
- listener.stateChanged(message, progress);
- }
- }
- private class DownloadListenerWrapper implements DownloadListener {
- private final AtomicReference<DownloadListener> wrapped = new AtomicReference<DownloadListener>(null);
- public void stateChanged(String fileName, float progress) {
- fileName = (new File(fileName)).getName();
- DownloadListener listener = wrapped.get();
- if (listener != null) {
- listener.stateChanged(fileName, progress);
- }
- }
- public void setDownloadListener(DownloadListener listener) {
- wrapped.set(listener);
- }
- }
- }