diff --git a/build.xml b/build.xml
index 0325971da..f8a57ad3b 100644
--- a/build.xml
+++ b/build.xml
@@ -20,6 +20,10 @@
+
+
+
+
@@ -51,6 +55,11 @@
+
+
+
+
+
diff --git a/src/org/modstats/IModstatsReporter.java b/src/org/modstats/IModstatsReporter.java
new file mode 100644
index 000000000..f275bf737
--- /dev/null
+++ b/src/org/modstats/IModstatsReporter.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats;
+
+public interface IModstatsReporter
+{
+ public void registerMod(Object mod);
+
+ public void doManualCheck();
+}
diff --git a/src/org/modstats/ModVersionData.java b/src/org/modstats/ModVersionData.java
new file mode 100644
index 000000000..4139a1536
--- /dev/null
+++ b/src/org/modstats/ModVersionData.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ModVersionData
+{
+ public String prefix;
+ public String name;
+ public String version;
+ public String downloadUrl;
+ public String changeLogUrl;
+
+ public Map extraFields;
+
+ public ModVersionData()
+ {
+ extraFields = new HashMap();
+ }
+
+ public ModVersionData(String prefix, String name, String version)
+ {
+ this.prefix = prefix;
+ this.name = name;
+ this.version = version;
+ extraFields = new HashMap();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((changeLogUrl == null) ? 0 : changeLogUrl.hashCode());
+ result = prime * result + ((downloadUrl == null) ? 0 : downloadUrl.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
+ result = prime * result + ((version == null) ? 0 : version.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ModVersionData other = (ModVersionData) obj;
+ if (changeLogUrl == null)
+ {
+ if (other.changeLogUrl != null)
+ return false;
+ }
+ else if (!changeLogUrl.equals(other.changeLogUrl))
+ return false;
+ if (downloadUrl == null)
+ {
+ if (other.downloadUrl != null)
+ return false;
+ }
+ else if (!downloadUrl.equals(other.downloadUrl))
+ return false;
+ if (name == null)
+ {
+ if (other.name != null)
+ return false;
+ }
+ else if (!name.equals(other.name))
+ return false;
+ if (prefix == null)
+ {
+ if (other.prefix != null)
+ return false;
+ }
+ else if (!prefix.equals(other.prefix))
+ return false;
+ if (version == null)
+ {
+ if (other.version != null)
+ return false;
+ }
+ else if (!version.equals(other.version))
+ return false;
+ return true;
+ }
+
+}
diff --git a/src/org/modstats/ModsUpdateEvent.java b/src/org/modstats/ModsUpdateEvent.java
new file mode 100644
index 000000000..649ef1004
--- /dev/null
+++ b/src/org/modstats/ModsUpdateEvent.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import net.minecraftforge.event.Cancelable;
+import net.minecraftforge.event.Event;
+import cpw.mods.fml.common.FMLLog;
+
+@Cancelable
+public class ModsUpdateEvent extends Event
+{
+ private List updatedMods;
+
+ public ModsUpdateEvent()
+ {
+ updatedMods = new LinkedList();
+ }
+
+ public void add(ModVersionData data)
+ {
+ if (!updatedMods.contains(data))
+ {
+ updatedMods.add(data);
+ }
+ else
+ {
+ FMLLog.info("ModsUpdateEvent shouldn't have same mods data", data);
+ }
+ }
+
+ public List getUpdatedMods()
+ {
+ return updatedMods;
+ }
+
+}
diff --git a/src/org/modstats/ModstatInfo.java b/src/org/modstats/ModstatInfo.java
new file mode 100644
index 000000000..19da5d86f
--- /dev/null
+++ b/src/org/modstats/ModstatInfo.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ModstatInfo
+{
+ /**
+ * Modstats mod prefix.
+ *
+ * @return
+ */
+ public String prefix();
+
+ /**
+ * Mod name. Use this if your mod doesn't have @Mod annotation
+ *
+ * @return
+ */
+ public String name() default "";
+
+ /**
+ * Mod version. Use this if your mod doesn't have @Mod annotation
+ *
+ * @return
+ */
+ public String version() default "";
+}
diff --git a/src/org/modstats/Modstats.java b/src/org/modstats/Modstats.java
new file mode 100644
index 000000000..8527e853e
--- /dev/null
+++ b/src/org/modstats/Modstats.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats;
+
+import cpw.mods.fml.common.FMLLog;
+
+public class Modstats
+{
+ private static final Modstats INSTANCE = new Modstats();
+ private static final String CLASS_TEMPLATE = "org.modstats.reporter.v%d.Reporter";
+ private IModstatsReporter reporter;
+
+ private Modstats()
+ {
+ reporter = locateReporter();
+ }
+
+ public IModstatsReporter getReporter()
+ {
+ return reporter;
+ }
+
+ private IModstatsReporter locateReporter()
+ {
+ int i = 1;
+ Class> latest = null;
+ while (i < 100)
+ {
+ try
+ {
+ Class> candidate = Class.forName(String.format(CLASS_TEMPLATE, i));
+ if (IModstatsReporter.class.isAssignableFrom(candidate))
+ {
+ latest = candidate;
+ }
+ }
+ catch (Exception e)
+ {
+ break;
+ }
+ i++;
+ }
+ if (latest == null)
+ {
+ FMLLog.warning("Modstats reporter class not found.");
+ }
+ else
+ {
+ try
+ {
+ return (IModstatsReporter) latest.newInstance();
+ }
+ catch (Exception e)
+ {
+ FMLLog.warning("Modstats reporter class can't be instantiated.");
+ }
+ }
+ return null;
+ }
+
+ public static Modstats instance()
+ {
+ return INSTANCE;
+ }
+
+}
diff --git a/src/org/modstats/reporter/v1/Config.java b/src/org/modstats/reporter/v1/Config.java
new file mode 100644
index 000000000..61eff08aa
--- /dev/null
+++ b/src/org/modstats/reporter/v1/Config.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats.reporter.v1;
+
+import java.io.File;
+
+import net.minecraftforge.common.Configuration;
+import net.minecraftforge.common.Property;
+import cpw.mods.fml.common.FMLLog;
+import cpw.mods.fml.common.Loader;
+
+public class Config
+{
+ private static final String CONFIG_NAME = "modstats.cfg";
+
+ public boolean allowUpdates;
+ public boolean betaNotifications;
+ public boolean forCurrentMinecraftVersion;
+ public boolean logOnly;
+
+ public Config()
+ {
+ File configLocation = new File(Loader.instance().getConfigDir(), CONFIG_NAME);
+ Configuration configuration = new Configuration(configLocation);
+ configuration.load();
+
+ Property prop = configuration.get("updates", "AllowUpdates", true);
+ prop.comment = "Allow to send current mod versions to the server and check for updates.\nIt allows to mod authors to see mod's popularity. Please don't disable it without necessity";
+ allowUpdates = prop.getBoolean(true);
+
+ prop = configuration.get("updates", "LogOnly", false);
+ prop.comment = "Don't display chat message, just add message to the log.";
+ logOnly = prop.getBoolean(false);
+
+ prop = configuration.get("updates", "BetaNotifications", false);
+ prop.comment = "Set true to receive notifications about beta versions. Otherwise you will only receive information about stable versions";
+ betaNotifications = prop.getBoolean(false);
+
+ prop = configuration.get("updates", "ForCurrentMinecraftVersion", false);
+ prop.comment = "Check for updates only for current MC version.\nEx:if you have MC 1.4.2 and ForCurrentMinecraftVersion is true, then you wouldn't receive notifications about versions for MC 1.4.5";
+ forCurrentMinecraftVersion = prop.getBoolean(false);
+
+ configuration.save();
+
+ FMLLog.info("[Modstats] Config loaded. allowUpdates: %b, betaNotification: %b, strict: %b", allowUpdates, betaNotifications, forCurrentMinecraftVersion);
+ }
+
+}
diff --git a/src/org/modstats/reporter/v1/DataSender.java b/src/org/modstats/reporter/v1/DataSender.java
new file mode 100644
index 000000000..475359b66
--- /dev/null
+++ b/src/org/modstats/reporter/v1/DataSender.java
@@ -0,0 +1,296 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats.reporter.v1;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.NetworkInterface;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.crash.CallableMinecraftVersion;
+import net.minecraftforge.common.MinecraftForge;
+
+import org.modstats.ModVersionData;
+import org.modstats.ModsUpdateEvent;
+
+import argo.jdom.JdomParser;
+import argo.jdom.JsonNode;
+import argo.jdom.JsonRootNode;
+import argo.jdom.JsonStringNode;
+import argo.saj.InvalidSyntaxException;
+
+import com.google.common.base.Charsets;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+import cpw.mods.fml.client.FMLClientHandler;
+import cpw.mods.fml.common.FMLCommonHandler;
+import cpw.mods.fml.common.FMLLog;
+import cpw.mods.fml.common.versioning.ComparableVersion;
+
+class DataSender extends Thread
+{
+ private static final String urlAutoTemplate = "http://modstats.org/api/v1/report?mc=%s&user=%s&data=%s&sign=%s&beta=%b&strict=%b";
+ private static final String urlManualTemplate = "http://modstats.org/api/v1/check?mc=%s&user=%s&data=%s&sign=%s&beta=%b&strict=%b";
+
+ private final Reporter reporter;
+ public final boolean manual;
+
+ public DataSender(Reporter reporter, boolean manual)
+ {
+ this.reporter = reporter;
+ this.manual = manual;
+ }
+
+ private String toHexString(byte[] bytes)
+ {
+ char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ char[] hexChars = new char[bytes.length * 2];
+ int v;
+ for (int j = 0; j < bytes.length; j++)
+ {
+ v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v / 16];
+ hexChars[j * 2 + 1] = hexArray[v % 16];
+ }
+ return new String(hexChars);
+ }
+
+ private String getPlayerId() throws IOException
+ {
+ File statDir = new File(FMLClientHandler.instance().getClient().mcDataDir, "stats");
+ if (!statDir.exists())
+ {
+ statDir.mkdirs();
+ }
+ String mac = "";
+ try
+ {
+ InetAddress address = InetAddress.getLocalHost();
+ NetworkInterface ni = NetworkInterface.getByInetAddress(address);
+ byte[] macArray = ni.getHardwareAddress();
+ if (macArray != null)
+ {
+ mac = toHexString(macArray);
+ }
+ }
+ catch (Exception ex)
+ {
+ }
+ File uidFile = new File(statDir, "player.uid");
+ if (uidFile.exists() && uidFile.canRead() && uidFile.length() == 32 + mac.length())
+ {
+ String data = Files.toString(uidFile, Charsets.US_ASCII);
+ String storedMac = data.substring(32);
+ if (storedMac.equalsIgnoreCase(mac))
+ return data.substring(0, 32);
+ }
+ uidFile.createNewFile();
+ if (uidFile.canWrite())
+ {
+ String uid = UUID.randomUUID().toString().replace("-", "");
+ FileOutputStream output = new FileOutputStream(uidFile);
+ output.write((uid + mac).getBytes());
+ output.close();
+ return uid;
+ }
+ return "";
+ }
+
+ private String getSignature(String data)
+ {
+ return Hashing.md5().hashString(data).toString();
+ }
+
+ private String getData()
+ {
+ StringBuilder b = new StringBuilder();
+ for (Map.Entry item : reporter.registeredMods.entrySet())
+ {
+ b.append(item.getKey()).append("+").append(item.getValue().version).append("$");
+ }
+ return b.toString();
+ }
+
+ private boolean checkIsNewer(String current, String received)
+ {
+ return new ComparableVersion(received).compareTo(new ComparableVersion(current)) > 0;
+ }
+
+ private void parseResponse(String response)
+ {
+ try
+ {
+ JsonRootNode json = (new JdomParser()).parse(response);
+ // empty result
+ if (!json.isNode("mods"))
+ {
+ FMLLog.info("[Modstats] Empty result");
+ return;
+ }
+ List modList = json.getArrayNode("mods");
+ ModsUpdateEvent event = new ModsUpdateEvent();
+ for (JsonNode modObject : modList)
+ {
+ String prefix = modObject.getStringValue("code");
+ if (!reporter.registeredMods.containsKey(prefix))
+ {
+ FMLLog.warning("[Modstats] Extra mod '%s' in service response", prefix);
+ continue;
+ }
+ String version = modObject.getStringValue("ver");
+ if (version == null || version.equals(reporter.registeredMods.get(prefix).version))
+ {
+ continue;
+ }
+ if (checkIsNewer(reporter.registeredMods.get(prefix).version, version))
+ {
+ ModVersionData data = new ModVersionData(prefix, reporter.registeredMods.get(prefix).name, version);
+ Map fields = modObject.getFields();
+ for (Map.Entry entry : fields.entrySet())
+ {
+ String fieldName = entry.getKey().getText();
+ if (fieldName.equals("code") || fieldName.equals("ver"))
+ continue;
+ if (!(entry.getValue() instanceof JsonStringNode))
+ {
+ FMLLog.warning(String.format("[Modstats] Too complex data in response for field '%s'.", fieldName));
+ continue;
+ }
+ String value = ((JsonStringNode) entry.getValue()).getText();
+ if (fieldName.equals("chlog"))
+ {
+ data.changeLogUrl = value;
+ }
+ else if (fieldName.equals("link"))
+ {
+ data.downloadUrl = value;
+ }
+ else
+ {
+ data.extraFields.put(fieldName, value);
+ }
+ }
+ event.add(data);
+ }
+
+ }
+ if (event.getUpdatedMods().size() > 0)
+ {
+ MinecraftForge.EVENT_BUS.post(event);
+ }
+ if (!event.isCanceled() && event.getUpdatedMods().size() > 0)
+ {
+ List updatedModsToOutput = event.getUpdatedMods();
+ StringBuilder builder = new StringBuilder("Updates found: ");
+ Iterator iterator = updatedModsToOutput.iterator();
+ while (iterator.hasNext())
+ {
+ ModVersionData modVersionData = iterator.next();
+ builder.append(modVersionData.name).append(" (").append(modVersionData.version).append(")").append(iterator.hasNext() ? "," : ".");
+ }
+ FMLLog.info("[Modstats] %s", builder.toString());
+ if (!reporter.config.logOnly && FMLCommonHandler.instance().getSide().isClient())
+ {
+ Minecraft mc = FMLClientHandler.instance().getClient();
+ int maxTries = 30;
+ while (mc.thePlayer == null && maxTries > 0)
+ {
+ try
+ {
+ sleep(1000);
+ }
+ catch (InterruptedException e)
+ {
+ }
+ maxTries--;
+ }
+ if (mc.thePlayer != null)
+ {
+ mc.thePlayer.addChatMessage(builder.toString());
+ }
+ }
+ }
+
+ }
+ catch (InvalidSyntaxException e)
+ {
+ FMLLog.warning("[Modstats] Can't parse response: '%s'.", e.getMessage());
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ String data = getData();
+ String playerId = getPlayerId();
+ String hash = getSignature(playerId + "!" + data);
+ String template = manual ? urlManualTemplate : urlAutoTemplate;
+ String mcVersion = new CallableMinecraftVersion(null).minecraftVersion();
+ URL url = new URL(String.format(template, mcVersion, playerId, data, hash, reporter.config.betaNotifications, reporter.config.forCurrentMinecraftVersion));
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(5000);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ String line;
+ String out = "";
+ while ((line = reader.readLine()) != null)
+ {
+ // in most cases it will contain just one line
+ out += line;
+ }
+ reader.close();
+ parseResponse(out);
+ }
+ catch (MalformedURLException e)
+ {
+ FMLLog.warning("[Modstats] Invalid stat report url");
+ }
+ catch (IOException e)
+ {
+ FMLLog.info("[Modstats] Stat wasn't reported '" + e.getMessage() + "'");
+ }
+ catch (Exception e)
+ {
+ FMLLog.warning("[Modstats] Something wrong: " + e.toString());
+ }
+ }
+}
diff --git a/src/org/modstats/reporter/v1/Reporter.java b/src/org/modstats/reporter/v1/Reporter.java
new file mode 100644
index 000000000..06b4b079d
--- /dev/null
+++ b/src/org/modstats/reporter/v1/Reporter.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) <2012>, Oleg Romanovskiy aka Shedar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the author nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.modstats.reporter.v1;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.ForgeSubscribe;
+import net.minecraftforge.event.world.WorldEvent;
+
+import org.modstats.IModstatsReporter;
+import org.modstats.ModVersionData;
+import org.modstats.ModstatInfo;
+
+import cpw.mods.fml.common.FMLCommonHandler;
+import cpw.mods.fml.common.FMLLog;
+import cpw.mods.fml.common.Mod;
+
+public class Reporter implements IModstatsReporter
+{
+
+ public Map registeredMods;
+ private DataSender sender;
+ public Config config;
+
+ /**
+ * At least one auto check was completed successfully
+ */
+ private boolean checkedAuto;
+
+ public Reporter()
+ {
+ checkedAuto = false;
+ registeredMods = new ConcurrentHashMap(2, 0.9f, 1);
+ MinecraftForge.EVENT_BUS.register(this);
+ config = new Config();
+ }
+
+ private void startCheck(boolean manual)
+ {
+ if (!config.allowUpdates)
+ return;
+ // only manual check is allowed on servers
+ if (!FMLCommonHandler.instance().getSide().isClient() && !manual)
+ return;
+ if (registeredMods.isEmpty())
+ return;
+ DataSender currentSender = sender;
+ if (!manual && checkedAuto)
+ return;
+ if (currentSender != null && (currentSender.manual == false || manual))
+ return;
+ currentSender = new DataSender(this, manual);
+ currentSender.start();
+ sender = currentSender;
+
+ }
+
+ @ForgeSubscribe
+ public void worldLoad(WorldEvent.Load event)
+ {
+ startCheck(false);
+ }
+
+ @Override
+ public void registerMod(Object mod)
+ {
+ if (!config.allowUpdates)
+ return;
+ if (mod == null)
+ {
+ FMLLog.warning("[Modstats] Can't register null mod.");
+ return;
+ }
+ ModstatInfo info = mod.getClass().getAnnotation(ModstatInfo.class);
+ if (info == null)
+ {
+ FMLLog.warning("[Modstats] ModstatsInfo annotation not found for given mod.");
+ return;
+ }
+
+ if (info.prefix() == null || info.prefix().equals(""))
+ {
+ FMLLog.warning("[Modstats] Mod prefix can't be empty.");
+ return;
+ }
+ Mod modData = mod.getClass().getAnnotation(Mod.class);
+ ModVersionData data;
+ if (modData == null)
+ {
+ if (info.name() == null || info.name().equals(""))
+ {
+ FMLLog.warning("[Modstats] Mod name can't be empty.");
+ return;
+ }
+ if (info.version() == null || info.version().equals(""))
+ {
+ FMLLog.warning("[Modstats] Mod version can't be empty.");
+ return;
+ }
+ data = new ModVersionData(info.prefix(), info.name(), info.version());
+ }
+ else
+ {
+ data = new ModVersionData(info.prefix(), modData.name(), modData.version());
+ }
+ registeredMods.put(info.prefix(), data);
+ }
+
+ @Override
+ public void doManualCheck()
+ {
+ startCheck(true);
+ }
+
+}
diff --git a/src/resonantinduction/ResonantInduction.java b/src/resonantinduction/ResonantInduction.java
index 5b5f345e7..10df04bbb 100644
--- a/src/resonantinduction/ResonantInduction.java
+++ b/src/resonantinduction/ResonantInduction.java
@@ -4,6 +4,9 @@ import java.io.File;
import java.util.Arrays;
import java.util.logging.Logger;
+import org.modstats.ModstatInfo;
+import org.modstats.Modstats;
+
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -44,6 +47,7 @@ import cpw.mods.fml.common.registry.LanguageRegistry;
*/
@Mod(modid = ResonantInduction.ID, name = ResonantInduction.NAME, version = ResonantInduction.VERSION)
@NetworkMod(channels = ResonantInduction.CHANNEL, clientSideRequired = true, serverSideRequired = false, packetHandler = PacketHandler.class)
+@ModstatInfo(prefix = "resonantin")
public class ResonantInduction
{
/**
@@ -127,6 +131,7 @@ public class ResonantInduction
{
LOGGER.setParent(FMLLog.getLogger());
NetworkRegistry.instance().registerGuiHandler(this, ResonantInduction.proxy);
+ Modstats.instance().getReporter().registerMod(this);
CONFIGURATION.load();