diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index 587e3f54d..526aae297 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -51,6 +51,7 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.PlayerEvent; import cpw.mods.fml.common.network.NetworkRegistry; public class ForgeModContainer extends DummyModContainer implements WorldAccessContainer @@ -252,6 +253,12 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC } } } + + @SubscribeEvent + public void playerLogin(PlayerEvent.PlayerLoggedInEvent event) + { + UsernameCache.setUsername(event.player.getGameProfile().getId(), event.player.getGameProfile().getName()); + } @Override public boolean registerBus(EventBus bus, LoadController controller) diff --git a/src/main/java/net/minecraftforge/common/MinecraftForge.java b/src/main/java/net/minecraftforge/common/MinecraftForge.java index 6d5ccc9ae..1e2f0158a 100644 --- a/src/main/java/net/minecraftforge/common/MinecraftForge.java +++ b/src/main/java/net/minecraftforge/common/MinecraftForge.java @@ -53,6 +53,8 @@ public class MinecraftForge //Force these classes to be defined, Should prevent derp error hiding. new CrashReport("ThisIsFake", new Exception("Not real")); + + UsernameCache.load(); } public static String getBrandingVersion() diff --git a/src/main/java/net/minecraftforge/common/UsernameCache.java b/src/main/java/net/minecraftforge/common/UsernameCache.java new file mode 100644 index 000000000..aac029be2 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/UsernameCache.java @@ -0,0 +1,202 @@ +package net.minecraftforge.common; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.io.Files; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; + +import cpw.mods.fml.relauncher.FMLInjectionData; + +/** + * Caches player's last known usernames + *

+ * Modders should use {@link #getLastKnownUsername(UUID)} to determine a players + * last known username.
+ * For convenience, {@link #getMap()} is provided to get an immutable copy of + * the caches underlying map. + */ +public final class UsernameCache { + + private static Map map = Maps.newHashMap(); + + private static final Charset charset = Charsets.UTF_8; + + private static final File saveFile = new File( /* The minecraft dir */(File) FMLInjectionData.data()[6], "usernamecache.json"); + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + private static final Logger log = LogManager.getLogger(UsernameCache.class); + + private UsernameCache() {} + + /** + * Set a player's current username + * + * @param uuid + * the player's {@link java.util.UUID UUID} + * @param username + * the player's username + */ + protected static void setUsername(UUID uuid, String username) + { + checkNotNull(uuid); + checkNotNull(username); + + if (username.equals(map.get(uuid))) return; + + map.put(uuid, username); + save(); + } + + /** + * Remove a player's username from the cache + * + * @param uuid + * the player's {@link java.util.UUID UUID} + * @return if the cache contained the user + */ + protected static boolean removeUsername(UUID uuid) + { + checkNotNull(uuid); + + if (map.remove(uuid) != null) + { + save(); + return true; + } + + return false; + } + + /** + * Get the player's last known username + *

+ * May be null + * + * @param uuid + * the player's {@link java.util.UUID UUID} + * @return the player's last known username, or null if the + * cache doesn't have a record of the last username + */ + @Nullable + public static String getLastKnownUsername(UUID uuid) + { + checkNotNull(uuid); + return map.get(uuid); + } + + /** + * Check if the cache contains the given player's username + * + * @param uuid + * the player's {@link java.util.UUID UUID} + * @return if the cache contains a username for the given player + */ + public static boolean containsUUID(UUID uuid) + { + checkNotNull(uuid); + return map.containsKey(uuid); + } + + /** + * Get an immutable copy of the cache's underlying map + * + * @return the map + */ + public static Map getMap() + { + return ImmutableMap.copyOf(map); + } + + /** + * Save the cache to file + */ + protected static void save() + { + new SaveThread(gson.toJson(map)).start(); + } + + /** + * Load the cache from file + */ + protected static void load() + { + if (!saveFile.exists()) return; + + try + { + + String json = Files.toString(saveFile, charset); + Type type = new TypeToken>() {}.getType(); + + map = gson.fromJson(json, type); + } + catch (JsonSyntaxException e) + { + log.error("Could not parse username cache file as valid json, deleting file", e); + saveFile.delete(); + } + catch (IOException e) + { + log.error("Failed to read username cache file from disk, deleting file", e); + saveFile.delete(); + } + finally + { + // Can sometimes occur when the json file is malformed + if (map == null) + { + map = Maps.newHashMap(); + } + } + } + + /** + * Used for saving the {@link com.google.gson.Gson#toJson(Object) Gson} + * representation of the cache to disk + */ + private static class SaveThread extends Thread { + + /** The data that will be saved to disk */ + private final String data; + + public SaveThread(String data) + { + this.data = data; + } + + @Override + public void run() + { + try + { + // Make sure we don't save when another thread is still saving + synchronized (saveFile) + { + Files.write(data, saveFile, charset); + } + } + catch (IOException e) + { + log.error("Failed to save username cache to file!", e); + } + } + } +} \ No newline at end of file