package de.take_weiland.mods.commons;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import com.google.common.io.InputSupplier;
import com.google.common.primitives.Ints;
import cpw.mods.fml.relauncher.CoreModManager;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import static java.lang.String.format;
/**
* <p>An automatic downloader for SevenCommons for you to ship with your mod.
* To use it, ship this file with your mod with a changed package name.
* If your Mod is a Coremod (IFMLLoadingPlugin), call {@link #setup()} from the constructor of your IFMLLoadingPlugin.
* If your Mod is a normal mod, add the following lines to the Jar-Manifest of your jar file:<pre>
* FMLCorePlugin: com.example.yourmod.SevenCommonsWrapper
* FMLCorePluginContainsFMLMod: true</pre></p>
* <p>Additionally add the following entry to your Jar-Manifest:<pre>
* SevenCommonsVersion: {MajorVersion}
* </pre></p>
*
* @author diesieben07
*/
@IFMLLoadingPlugin.MCVersion(SevenCommonsWrapper.MC_VERSION)
public final class SevenCommonsWrapper implements IFMLLoadingPlugin {
public static void setup() {
if (Launch.blackboard.containsKey(MARKER)) {
return;
}
Launch.blackboard.put(MARKER, Boolean.TRUE);
Set<Integer> reqVersions = allReqVersions();
Set<Integer> presVersions = allPresVersions();
if (reqVersions.size() != 1) {
System.err.println("Multiple SevenCommons versions requested: " + reqVersions);
System.exit(1);
} else if (presVersions.size() >= 2) {
System.err.println("Multiple SevenCommons versions installed: " + presVersions);
System.exit(1);
} else {
int reqVersion = Iterables.getOnlyElement(reqVersions);
if (presVersions.isEmpty()) {
doDownload(reqVersion);
} else {
int presVersion = Iterables.getOnlyElement(presVersions);
if (presVersion != reqVersion) {
System.err.println("Requested SevenCommons version was: " + reqVersion + ", but " + presVersion + " is installed.");
} else {
System.out.println("Requested SevenCommons version already installed.");
}
}
}
}
private static final String MARKER = "de.take_weiland.sevencommons.wrapper";
private static final String REQ_VERSION_ENTRY = "SevenCommonsVersion";
private static final String PRESENT_VERSION_ENTRY = "SevenCommonsInstalledVersion";
static final String MC_VERSION = "1.6.4";
private static final String JAR_REQUEST_URL = "http://mods.take-weiland.de/sevencommons_version.php?v=%d";
private static final String SC_MAIN_CLASS = "de.take_weiland.mods.commons.internal.SevenCommonsLoader";
private static ImmutableSet<Integer> allReqVersions() {
return allVersions(getVersionFunc(REQ_VERSION_ENTRY));
}
private static ImmutableSet<Integer> allPresVersions() {
return allVersions(getVersionFunc(PRESENT_VERSION_ENTRY));
}
private static ImmutableSet<Integer> allVersions(Function<JarFile, Integer> getVersionFunc) {
return FluentIterable.from(getAllMods())
.transform(getVersionFunc)
.filter(Predicates.notNull())
.toSet();
}
private static Integer getVersion(JarFile mod, String versionKey) {
Manifest mf;
try {
mf = mod.getManifest();
} catch (IOException e) {
return null;
}
if (mf != null) {
String version = mf.getMainAttributes().getValue(versionKey);
if (version == null) {
return null;
} else {
Integer i = Ints.tryParse(version);
if (i == null || i < 0) {
throw new RuntimeException("Invalid SevenCommons version in JarFile: " + mod.getName());
}
return i;
}
} else {
return null;
}
}
private static Function<JarFile, Integer> getVersionFunc(final String key) {
return new Function<JarFile, Integer>() {
@Override
public Integer apply(JarFile input) {
return getVersion(input, key);
}
};
}
private static Iterable<JarFile> getAllMods() {
File mcDir = getMCDir();
File mods1 = new File(mcDir, "/mods/");
File mods2 = new File(mods1, "/" + MC_VERSION + "/");
Iterable<File> files = Iterables.concat(getMods(mods1), getMods(mods2));
return Iterables.transform(files, new Function<File, JarFile>() {
@Override
public JarFile apply(File input) {
try {
return new JarFile(input);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
});
}
private static Iterable<File> getMods(File modDir) {
if (!modDir.isDirectory()) {
return ImmutableList.of();
} else {
return Arrays.asList(modDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".jar");
}
}));
}
}
private static File getMCDir() {
return Launch.minecraftHome == null ? new File(".") : Launch.minecraftHome;
}
private static void doDownload(int majorVersion) {
System.out.println("Downloading SevenCommons. Major Version: " + majorVersion);
Downloader loader = new Downloader(majorVersion);
loader.start();
try {
loader.join();
} catch (InterruptedException e) {
loader.interrupt();
return;
}
if (loader.errored) {
System.err.println("Download failed. Exiting.");
System.exit(1);
} else {
loadSevenCommons(loader.target);
}
}
private static void loadSevenCommons(File file) {
try {
Launch.classLoader.addURL(file.toURI().toURL());
CoreModManager.getLoadedCoremods().add(file.getName());
Method loadCoremod = CoreModManager.class.getDeclaredMethod("loadCoreMod", LaunchClassLoader.class, String.class, File.class);
loadCoremod.setAccessible(true);
loadCoremod.invoke(null, Launch.classLoader, SC_MAIN_CLASS, file);
} catch (Exception e) {
System.err.println("Failed to load SevenCommons as a Coremod!");
e.printStackTrace();
System.exit(1);
}
}
private static DownloadDisplay newDisplay(Thread downloadThread) {
return new ConsoleDisplay(downloadThread);
}
private static class Downloader extends Thread {
private final DownloadDisplay display;
private final int majorVersion;
boolean errored;
File target;
Downloader(int majorVersion) {
this.majorVersion = majorVersion;
display = newDisplay(this);
}
@Override
public void run() {
URL source = getDownloadURL(majorVersion);
if (source == null) {
return;
}
target = getTarget(source);
if (!target.getParentFile().mkdirs()) {
error("Failed to create mods folder: " + target.getParent());
return;
}
ReadableByteChannel in = null;
WritableByteChannel out = null;
try {
display.setup();
URLConnection conn = source.openConnection();
in = monitor(Channels.newChannel(conn.getInputStream()), conn.getContentLength());
out = Channels.newChannel(new FileOutputStream(target));
ByteStreams.copy(in, out);
} catch (IOException e) {
e.printStackTrace();
error("Failed to download file: " + e.getMessage());
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
private ReadableByteChannel monitor(ReadableByteChannel channel, int size) {
return new MonitorChannel(channel, display, size < 0 ? Integer.MAX_VALUE : size);
}
private URL getDownloadURL(int majorVersion) {
String response = getContents(getRequestURLFor(majorVersion));
if (response.startsWith("err:")) {
error("Failed to get Download URL for major version " + majorVersion + ". " + response);
return null;
}
try {
return new URL(response);
} catch (MalformedURLException e) {
error("Got invalid download URL: " + response);
return null;
}
}
private static File getTarget(URL downloadURL) {
String filename = Files.getNameWithoutExtension(downloadURL.getFile()) + ".jar";
return new File(getMCDir(), "/mods/" + MC_VERSION + "/" + filename);
}
private static URL getRequestURLFor(int majorVersion) {
try {
return new URL(format(JAR_REQUEST_URL, majorVersion));
} catch (MalformedURLException e) {
throw Throwables.propagate(e);
}
}
private static String getContents(final URL url) {
try {
return CharStreams.toString(new InputSupplier<Reader>() {
@Override
public Reader getInput() throws IOException {
return new InputStreamReader(url.openStream());
}
});
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
private void error(String err) {
display.displayError(err);
errored = true;
}
}
private static final class MonitorChannel implements ReadableByteChannel {
private final ReadableByteChannel delegate;
private final DownloadDisplay display;
private final int size;
private int read;
MonitorChannel(ReadableByteChannel delegate, DownloadDisplay display, int size) {
this.delegate = delegate;
this.display = display;
this.size = size;
}
@Override
public int read(ByteBuffer dst) throws IOException {
int n = delegate.read(dst);
if (n > 0) {
read += n;
display.displayProgress((double) read / (double) size);
}
return n;
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
private static abstract class DownloadDisplay {
final Thread downloadThread;
DownloadDisplay(Thread downloadThread) {
this.downloadThread = downloadThread;
}
final void stop() {
downloadThread.interrupt();
}
abstract void setup();
abstract void displayProgress(double progress);
abstract void displayError(String err);
}
private static final class ConsoleDisplay extends DownloadDisplay {
private int lastPercent = -1;
private long lastTime;
ConsoleDisplay(Thread downloadThread) {
super(downloadThread);
}
@Override
void setup() {
displayProgress(0);
}
@Override
void displayProgress(double progress) {
int p = (int) Math.floor(progress * 100);
if (p == lastPercent) {
return;
}
long now = System.currentTimeMillis();
if (p != 0 && p != 100 && Math.abs(lastTime - now) <= 500) {
return;
}
lastTime = now;
lastPercent = p;
System.out.println(p + "% complete...");
}
@Override
void displayError(String err) {
System.out.println("Download failed with: " + err);
}
}
public SevenCommonsWrapper() {
setup();
}
@Override
public String[] getASMTransformerClass() {
return new String[0];
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) { }
}