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();