/** * (C) 2015 NPException */ package nl.lang2619.bagginses.gameanalytics; import nl.lang2619.bagginses.gameanalytics.events.GABusinessEvent; import nl.lang2619.bagginses.gameanalytics.events.GADesignEvent; import nl.lang2619.bagginses.gameanalytics.events.GAErrorEvent; import nl.lang2619.bagginses.gameanalytics.events.GAEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; import java.util.UUID; /** * This API can be used to collect analytics data about your mod, using the * service at <a href="http://www.gameanalytics.com/">GameAnalytics.com</a>.<br> * <br> * Even though this API was initially designed to be used by MinecraftMods, it * can be used for every other Java game as well as soon as you rip out the * <b>package-info.java</b>. * * @author NPException * */ public abstract class Analytics { public abstract boolean isActive(); public abstract String gameKey(); public abstract String secretKey(); public abstract String build(); public abstract String userPrefix(); ///////////////////////////////////////////////////// // Methods you don't need to override necessarily, // // but can if you want to do something specific // ///////////////////////////////////////////////////// private String sessionID; private String userID; public synchronized String getUserID() { if (userID == null) { userID = "unknown"; try { Properties config = loadConfig(); UUID uuid; try { uuid = UUID.fromString(config.getProperty("user_id")); } catch (Exception e) { uuid = UUID.randomUUID(); config.setProperty("user_id", uuid.toString()); saveConfig(config); } userID = uuid.toString(); } catch (Exception e) { System.err.println("Error when loading analytics config"); e.printStackTrace(System.err); } } return userPrefix() + userID; } public synchronized String getSessionID() { if (sessionID == null) { sessionID = String.valueOf(System.currentTimeMillis()); } return sessionID; } protected String getAnaylitcsConfigRootPath() { return ".analytics"; } protected Properties loadConfig() throws IOException { synchronized (getClass()) { Properties config = new Properties(); File file = getConfigFile(); if (!file.exists()) { file.getParentFile().mkdirs(); file.createNewFile(); } try (FileInputStream fis = new FileInputStream(file)) { config.load(fis); } return config; } } protected void saveConfig(Properties config) throws IOException { synchronized (getClass()) { File file = getConfigFile(); if (!file.exists()) { file.getParentFile().mkdirs(); file.createNewFile(); } try (FileOutputStream fos = new FileOutputStream(file)) { config.store(fos, null); } } } protected String getConfigFileName() { return getClass().getCanonicalName(); } private File getConfigFile() { return new File(getAnaylitcsConfigRootPath(), getConfigFileName() + ".properties"); } //////////////////////////// // Methods to send events // //////////////////////////// public final void event(GAEvent event, boolean immediate) { if (isActive()) { if (immediate) { EventHandler.queueImmediateSend(event); } else { EventHandler.add(event); } } } public final void eventError(GAErrorEvent.Severity severity, String message) { if (isActive()) { switch (severity) { case critical: case error: EventHandler.queueImmediateSend(new GAErrorEvent(this, severity, message)); break; case warning: case info: case debug: EventHandler.add(new GAErrorEvent(this, severity, message)); break; default: System.err.println("Unhandled severity for analytics: " + severity.name()); break; } } } /** * Sends an error asap, using a non-deamon thread. * * @param severity * @param message */ public final void eventErrorNow(GAErrorEvent.Severity severity, String message) { if (isActive()) { EventHandler.sendErrorNow(new GAErrorEvent(this, severity, message), true); } } /** * Sends an error NOW, without using a seperate thread.<br> * Beware that this can be quite expensive, so you should only use this when * your game is in an unrecoverable downward crash spiral anyway. * * @param severity * @param message */ protected final void eventErrorNOW(GAErrorEvent.Severity severity, String message) { if (isActive()) { EventHandler.sendErrorNow(new GAErrorEvent(this, severity, message), false); } } public final void eventDesign(String eventID) { if (isActive()) { EventHandler.add(new GADesignEvent(this, eventID, null, null)); } } public final void eventDesign(String eventID, String area) { if (isActive()) { EventHandler.add(new GADesignEvent(this, eventID, area, null)); } } public final void eventDesign(String eventID, Number value) { if (isActive()) { EventHandler.add(new GADesignEvent(this, eventID, null, value)); } } public final void eventDesign(String eventID, String area, Number value) { if (isActive()) { EventHandler.add(new GADesignEvent(this, eventID, area, value)); } } /** * Similar to {@link #eventDesign(String, String, Number)}.<br> * <br> * Currency must be one that is specifically accepted by GA. see <a href= * "http://support.gameanalytics.com/hc/en-us/articles/200841576-Supported-currencies" * >Supported currencies</a><br> * <br> * The amount is the given monetary unit (currency) times 100. So in case of * USD the amount would be given in cents, so to speak. * * @param an * @param eventID * @param amount * @param currency */ public final void eventBusiness(String eventID, String area, int amount, String currency) { if (isActive()) { EventHandler.queueImmediateSend(new GABusinessEvent(this, eventID, area, amount, currency)); } } ///////////////////////////////////////////////////////////////// // internal stuff. you should not need to bother about this :) // ///////////////////////////////////////////////////////////////// public final KeyPair keyPair() { if (keyPair == null) { keyPair = new KeyPair(); } return keyPair; } private KeyPair keyPair; public final class KeyPair { public final String gameKey = gameKey(); public final String secretKey = secretKey(); @Override public int hashCode() { return gameKey.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof KeyPair)) return false; KeyPair other = (KeyPair) obj; return gameKey.equals(other.gameKey) && secretKey.equals(other.secretKey); } @Override public String toString() { return "KeyPair: hasGameKey=" + (gameKey != null) + ", hasSecretKey=" + (secretKey != null); } } }