com.enderville.enderinstaller.util.InstallScript.java Source code

Java tutorial

Introduction

Here is the source code for com.enderville.enderinstaller.util.InstallScript.java

Source

/**
 * EnderInstaller - Installer for the EnderPack Modpack.
 * Copyright (C) 2012, EnderVille.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.enderville.enderinstaller.util;

import java.io.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;

/**
 * The script that does the work to install mods.
 */
public class InstallScript {

    private static final Logger LOGGER = Logger.getLogger(InstallScript.class);

    /**
     * Repackages all the files in the tmp directory to the new minecraft.jar
     *
     * @param tmp The temp directory where mods were installed.
     * @param mcjar The location to save the new minecraft.jar.
     * @throws IOException
     */
    public static void repackMCJar(File tmp, File mcjar) throws IOException {
        byte[] dat = new byte[4 * 1024];

        JarOutputStream jarout = new JarOutputStream(FileUtils.openOutputStream(mcjar));

        Queue<File> queue = new LinkedList<File>();
        for (File f : tmp.listFiles()) {
            queue.add(f);
        }

        while (!queue.isEmpty()) {
            File f = queue.poll();
            if (f.isDirectory()) {
                for (File child : f.listFiles()) {
                    queue.add(child);
                }
            } else {
                //TODO need a better way to do this
                String name = f.getPath().substring(tmp.getPath().length() + 1);
                //TODO is this formatting really required for jars?
                name = name.replace("\\", "/");
                if (f.isDirectory() && !name.endsWith("/")) {
                    name = name + "/";
                }
                JarEntry entry = new JarEntry(name);
                jarout.putNextEntry(entry);

                FileInputStream in = new FileInputStream(f);
                int len = -1;
                while ((len = in.read(dat)) > 0) {
                    jarout.write(dat, 0, len);
                }
                in.close();
            }
            jarout.closeEntry();
        }
        jarout.close();

    }

    /**
     * Unpacks all the contents of the old minecraft.jar to the temp directory.
     *
     * @param tmpdir The temp directory to unpack to.
     * @param mcjar The location of the old minecraft.jar
     * @throws IOException
     */
    public static void unpackMCJar(File tmpdir, File mcjar) throws IOException {
        byte[] dat = new byte[4 * 1024];
        JarFile jar = new JarFile(mcjar);
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String name = entry.getName();
            //This gets rid of META-INF if it exists.
            if (name.startsWith("META-INF")) {
                continue;
            }
            InputStream in = jar.getInputStream(entry);
            File dest = new File(FilenameUtils.concat(tmpdir.getPath(), name));
            if (entry.isDirectory()) {
                //I don't think this actually happens
                LOGGER.warn("Found a directory while iterating over jar.");
                dest.mkdirs();
            } else if (!dest.getParentFile().exists()) {
                if (!dest.getParentFile().mkdirs()) {
                    throw new IOException("Couldn't create directory for " + name);
                }
            }
            FileOutputStream out = new FileOutputStream(dest);
            int len = -1;
            while ((len = in.read(dat)) > 0) {
                out.write(dat, 0, len);
            }
            out.flush();
            out.close();
            in.close();
        }
    }

    /**
     * Attempts to create a temporary directory in the installer folder to
     * unpack the jar.
     *
     * @return
     * @throws IOException
     */
    private static File getTempDir() throws IOException {
        Random rand = new Random();
        String hex = Integer.toHexString(rand.nextInt(Integer.MAX_VALUE));
        File tmp = new File(FilenameUtils.concat(InstallerConfig.getInstallerDir(), hex + "/"));
        int t = 0;
        while (tmp.exists() && t < 10) {
            hex = Integer.toHexString(rand.nextInt(Integer.MAX_VALUE));
            tmp = new File(FilenameUtils.normalize("./" + hex + "/"));
            t++;
        }
        if (tmp.exists()) {
            throw new IOException("Error creating temporary folder. Too many failures.");
        }
        return tmp;
    }

    /**
     * Checks the md5 of the minecraft.jar and whether the mods folder exists in
     * order to warn the user.
     *
     * @return A string containing warnings for the user, or null if there were
     * none.
     */
    public static String preInstallCheck() {
        //TODO check the number in .minecraft/bin/version file?
        try {
            InputStream mcJarIn = new FileInputStream(InstallerConfig.getMinecraftJar());
            String digest = DigestUtils.md5Hex(mcJarIn);
            mcJarIn.close();
            boolean jarValid = InstallerConfig.getMinecraftJarMD5().equalsIgnoreCase(digest);
            File modsDir = new File(InstallerConfig.getMinecraftModsFolder());
            boolean noMods = !modsDir.exists() || modsDir.listFiles().length == 0;
            String msg = null;
            if (!jarValid) {
                msg = String.format("Your minecraft.jar has been modified or is not the correct version (%s).",
                        InstallerConfig.getMinecraftVersion());
            }
            if (!noMods) {
                msg = (msg == null ? "" : msg + "\n")
                        + "You already have mods installed to the minecraft mods folder.";
            }
            return msg;
        } catch (Exception e) {
            LOGGER.error("Pre-installation check error", e);
            return "There was an error verifying your minecraft installation. " + e.getMessage();
        }
    }

    /**
     * Installs the specified list of mods, updating the gui as it goes.
     *
     * @param mods The list of mods to install.
     * @param text The text area to update with logging statements.
     * @param progressBar The progress bar to update.
     */
    public static void guiInstall(List<String> mods, JTextArea text, JProgressBar progressBar) {

        //Create backups to restore if the install fails.
        try {
            createBackup();
        } catch (IOException e) {
            text.append("Failed to create backup copies of minecraft.jar and mods folder");
            LOGGER.error("Failed to create backup copies of minecraft.jar and mods folder", e);
            return;
        }

        //Create a temp directory where we can unpack the jar and install mods.
        File tmp;
        try {
            tmp = getTempDir();
        } catch (IOException e) {
            text.append("Error creating temp directory!");
            LOGGER.error("Install Error", e);
            return;
        }
        if (!tmp.mkdirs()) {
            text.append("Error creating temp directory!");
            return;
        }

        File mcDir = new File(InstallerConfig.getMinecraftFolder());
        File mcJar = new File(InstallerConfig.getMinecraftJar());
        File reqDir = new File(InstallerConfig.getRequiredModsFolder());

        //Calculate the number of "tasks" for the progress bar.
        int reqMods = 0;
        for (File f : reqDir.listFiles()) {
            if (f.isDirectory()) {
                reqMods++;
            }
        }
        //3 "other" steps: unpack, repack, delete temp
        int baseTasks = 3;
        int taskSize = reqMods + mods.size() + baseTasks;
        progressBar.setMinimum(0);
        progressBar.setMaximum(taskSize);
        int task = 1;

        try {
            text.append("Unpacking minecraft.jar\n");
            unpackMCJar(tmp, mcJar);
            progressBar.setValue(task++);

            text.append("Installing Core mods\n");
            //TODO specific ordering required!
            for (File mod : reqDir.listFiles()) {
                if (!mod.isDirectory()) {
                    continue;
                }
                String name = mod.getName();
                text.append("...Installing " + name + "\n");
                installMod(mod, tmp, mcDir);
                progressBar.setValue(task++);
            }

            if (!mods.isEmpty()) {
                text.append("Installing Extra mods\n");
                //TODO specific ordering required!
                for (String name : mods) {
                    File mod = new File(FilenameUtils
                            .normalize(FilenameUtils.concat(InstallerConfig.getExtraModsFolder(), name)));
                    text.append("...Installing " + name + "\n");
                    installMod(mod, tmp, mcDir);
                    progressBar.setValue(task++);
                }
            }

            text.append("Repacking minecraft.jar\n");
            repackMCJar(tmp, mcJar);
            progressBar.setValue(task++);
        } catch (Exception e) {
            text.append("!!!Error installing mods!!!");
            LOGGER.error("Installation error", e);
            try {
                restoreBackup();
            } catch (IOException ioe) {
                text.append("Error while restoring backup files minecraft.jar.backup and mods_backup folder\n!");
                LOGGER.error("Error while restoring backup files minecraft.jar.backup and mods_backup folder!",
                        ioe);
            }
        }

        text.append("Deleting temporary files\n");
        try {
            FileUtils.deleteDirectory(tmp);
            progressBar.setValue(task++);
        } catch (IOException e) {
            text.append("Error deleting temporary files!\n");
            LOGGER.error("Install Error", e);
            return;
        }
        text.append("Finished!");

    }

    //TODO move this elsewhere and make it more general
    private static final String[] otherThingsToBackup = { "millenaire" };

    /**
     * Make backup copies of important files/folders in case the backup fails.
     *
     * @throws IOException
     */
    private static void createBackup() throws IOException {
        //TODO what other folders to backup?
        FileUtils.copyFile(new File(InstallerConfig.getMinecraftJar()),
                new File(InstallerConfig.getMinecraftJar() + ".backup"));
        File mods = new File(InstallerConfig.getMinecraftModsFolder());
        File modsBackup = new File(InstallerConfig.getMinecraftModsFolder() + "_backup/");
        if (modsBackup.exists()) {
            FileUtils.deleteDirectory(modsBackup);
        }
        if (mods.exists()) {
            FileUtils.copyDirectory(mods, modsBackup);
        }

        for (String name : otherThingsToBackup) {
            String fname = FilenameUtils
                    .normalize(FilenameUtils.concat(InstallerConfig.getMinecraftFolder(), name));
            String fnameBackup = fname + "_backup";
            File f = new File(fname);
            File backup = new File(fnameBackup);
            if (backup.exists()) {
                FileUtils.deleteDirectory(backup);
            }
            if (f.exists()) {
                FileUtils.copyDirectory(f, backup);
            }
        }
    }

    /**
     * If the install fails, restore everything that we backed up.
     *
     * @throws IOException
     */
    private static void restoreBackup() throws IOException {
        //TODO what other folders to restore?
        FileUtils.copyFile(new File(InstallerConfig.getMinecraftJar() + ".backup"),
                new File(InstallerConfig.getMinecraftJar()));
        File mods = new File(InstallerConfig.getMinecraftModsFolder());
        File modsBackup = new File(InstallerConfig.getMinecraftModsFolder() + "_backup");
        if (modsBackup.exists()) {
            FileUtils.deleteDirectory(mods);
            FileUtils.copyDirectory(modsBackup, mods);
        }
        for (String name : otherThingsToBackup) {
            String fname = FilenameUtils
                    .normalize(FilenameUtils.concat(InstallerConfig.getMinecraftFolder(), name));
            String fnameBackup = fname + "_backup";
            File f = new File(fname);
            File backup = new File(fnameBackup);
            if (backup.exists()) {
                FileUtils.copyDirectory(backup, f);
            }
        }
    }

    /**
     * Installs the mod specified by folder.
     *
     * @param modDir The directory of the mod to install.
     * @param jarDir The temp directory with the contents of the jar.
     * @param mcDir The target minecraft installation folder.
     * @throws IOException
     */
    private static void installMod(File modDir, File jarDir, File mcDir) throws IOException {
        for (File dir : modDir.listFiles()) {
            if (!dir.isDirectory()) {
                continue;
            }
            if (dir.getName().equals("jar")) {
                FileUtils.copyDirectory(dir, jarDir);
            } else if (dir.getName().equals("resources")) {
                FileUtils.copyDirectory(dir, mcDir);
            }
        }
    }
}