diff --git a/.gitignore b/.gitignore index 0824d119..5e182411 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ CHANGELOG /src/minecraft/* !/src/minecraft/fluidmech/ !/src/minecraft/hydraulic/ +!/src/minecraft/org/ !/resources/ !/models/ !info.txt diff --git a/src/minecraft/org/modstats/IModstatsReporter.java b/src/minecraft/org/modstats/IModstatsReporter.java new file mode 100644 index 00000000..8fe62722 --- /dev/null +++ b/src/minecraft/org/modstats/IModstatsReporter.java @@ -0,0 +1,34 @@ +/** + * 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/minecraft/org/modstats/ModVersionData.java b/src/minecraft/org/modstats/ModVersionData.java new file mode 100644 index 00000000..ab3fbcd0 --- /dev/null +++ b/src/minecraft/org/modstats/ModVersionData.java @@ -0,0 +1,114 @@ +/** + * 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/minecraft/org/modstats/ModsUpdateEvent.java b/src/minecraft/org/modstats/ModsUpdateEvent.java new file mode 100644 index 00000000..1f12aaf5 --- /dev/null +++ b/src/minecraft/org/modstats/ModsUpdateEvent.java @@ -0,0 +1,65 @@ +/** + * 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 cpw.mods.fml.common.FMLLog; + +import net.minecraftforge.event.Cancelable; +import net.minecraftforge.event.Event; + +@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/minecraft/org/modstats/ModstatInfo.java b/src/minecraft/org/modstats/ModstatInfo.java new file mode 100644 index 00000000..1927e52f --- /dev/null +++ b/src/minecraft/org/modstats/ModstatInfo.java @@ -0,0 +1,56 @@ +/** + * 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/minecraft/org/modstats/Modstats.java b/src/minecraft/org/modstats/Modstats.java new file mode 100644 index 00000000..c9753e4d --- /dev/null +++ b/src/minecraft/org/modstats/Modstats.java @@ -0,0 +1,89 @@ +/** + * 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/minecraft/org/modstats/reporter/v1/Config.java b/src/minecraft/org/modstats/reporter/v1/Config.java new file mode 100644 index 00000000..708e3a93 --- /dev/null +++ b/src/minecraft/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/minecraft/org/modstats/reporter/v1/DataSender.java b/src/minecraft/org/modstats/reporter/v1/DataSender.java new file mode 100644 index 00000000..5a216159 --- /dev/null +++ b/src/minecraft/org/modstats/reporter/v1/DataSender.java @@ -0,0 +1,294 @@ +/** + * 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(Minecraft.getMinecraftDir(), "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/minecraft/org/modstats/reporter/v1/Reporter.java b/src/minecraft/org/modstats/reporter/v1/Reporter.java new file mode 100644 index 00000000..2744b6d7 --- /dev/null +++ b/src/minecraft/org/modstats/reporter/v1/Reporter.java @@ -0,0 +1,145 @@ +/** + * 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); + } + +}