From eee82e463f7818928f0472496137614f1b5d8950 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 14 Oct 2021 00:11:43 -0400 Subject: [PATCH 01/38] Store username history inside userdata --- .../main/java/com/earth2me/essentials/IUser.java | 4 ++++ .../java/com/earth2me/essentials/UserData.java | 16 ++++++++++++++++ .../essentials/commands/Commandseen.java | 8 ++++---- .../config/EssentialsConfiguration.java | 4 ++++ .../config/holders/UserConfigHolder.java | 14 ++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/IUser.java b/Essentials/src/main/java/com/earth2me/essentials/IUser.java index c78e18407ab..cd65cf1df50 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IUser.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IUser.java @@ -238,4 +238,8 @@ public interface IUser { void setToggleShout(boolean toggleShout); boolean isToggleShout(); + + List getPastUsernames(); + + void addPastUsername(String username); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserData.java b/Essentials/src/main/java/com/earth2me/essentials/UserData.java index 313b984cc90..08a2b65f630 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserData.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserData.java @@ -589,6 +589,11 @@ public String getLastAccountName() { } public void setLastAccountName(final String lastAccountName) { + if (getLastAccountName() != null && !getLastAccountName().equals(lastAccountName)) { + final List usernames = holder.pastUsernames(); + usernames.add(0, getLastAccountName()); + holder.pastUsernames(usernames); + } holder.lastAccountName(lastAccountName); config.save(); ess.getUserMap().trackUUID(getConfigUUID(), lastAccountName, true); @@ -722,6 +727,17 @@ public void setBaltopExemptCache(boolean baltopExempt) { config.save(); } + public List getPastUsernames() { + return holder.pastUsernames(); + } + + public void addPastUsername(String username) { + final List usernames = holder.pastUsernames(); + usernames.add(0, username); + holder.pastUsernames(usernames); + config.save(); + } + public UUID getConfigUUID() { return config.getUuid(); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java index c293e7e47f2..84d58e4f8ef 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java @@ -106,8 +106,8 @@ private void seenOnline(final CommandSource sender, final User user, final boole sender.sendMessage(tl("whoisUuid", user.getBase().getUniqueId().toString())); } - final List history = ess.getUserMap().getUserHistory(user.getBase().getUniqueId()); - if (history != null && history.size() > 1) { + final List history = user.getPastUsernames(); + if (history != null && !history.isEmpty()) { sender.sendMessage(tl("seenAccounts", StringUtil.joinListSkip(", ", user.getName(), history))); } @@ -145,8 +145,8 @@ private void seenOffline(final CommandSource sender, final User user, final bool sender.sendMessage(tl("userUnknown", user.getName())); } - final List history = ess.getUserMap().getUserHistory(user.getBase().getUniqueId()); - if (history != null && history.size() > 1) { + final List history = user.getPastUsernames(); + if (history != null && !history.isEmpty()) { sender.sendMessage(tl("seenAccounts", StringUtil.joinListSkip(", ", user.getName(), history))); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java index 1e9988a5378..36c9ae70cd1 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java @@ -421,6 +421,10 @@ public void stopTransaction(final boolean blocking) { } } + public boolean isTransaction() { + return transaction.get(); + } + public void setSaveHook(Runnable saveHook) { this.saveHook = saveHook; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java b/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java index 5f1de20c26f..5e1e7aec7f0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java @@ -322,6 +322,20 @@ public void baltopExempt(final boolean value) { this.baltopExempt = value; } + @DeleteOnEmpty + private @MonotonicNonNull List pastUsernames; + + public List pastUsernames() { + if (this.pastUsernames == null) { + this.pastUsernames = new ArrayList<>(); + } + return this.pastUsernames; + } + + public void pastUsernames(List value) { + this.pastUsernames = value; + } + private @NonNull Timestamps timestamps = new Timestamps(); public Timestamps timestamps() { From c8a39da63c2e06c4f80cced8edf32df627d916dd Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 14 Oct 2021 19:59:23 -0400 Subject: [PATCH 02/38] Remove legacy uuidconvert and uuidtest subcommands --- .../commands/Commandessentials.java | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 51310d35e1f..3a1e05788e7 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -1,7 +1,6 @@ package com.earth2me.essentials.commands; import com.earth2me.essentials.CommandSource; -import com.earth2me.essentials.EssentialsUpgrade; import com.earth2me.essentials.User; import com.earth2me.essentials.UserMap; import com.earth2me.essentials.economy.EconomyLayer; @@ -12,7 +11,6 @@ import com.earth2me.essentials.utils.NumberUtil; import com.earth2me.essentials.utils.PasteUtil; import com.earth2me.essentials.utils.VersionUtil; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.gson.JsonArray; @@ -135,12 +133,6 @@ public void run(final Server server, final CommandSource sender, final String co case "homes": runHomes(server, sender, commandLabel, args); break; - case "uuidconvert": - runUUIDConvert(server, sender, commandLabel, args); - break; - case "uuidtest": - runUUIDTest(server, sender, commandLabel, args); - break; // "#EasterEgg" case "nya": @@ -511,54 +503,6 @@ private void runHomes(final Server server, final CommandSource sender, final Str } } - // Forces a rerun of userdata UUID conversion. - private void runUUIDConvert(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { - sender.sendMessage("Starting Essentials UUID userdata conversion; this may lag the server."); - - final Boolean ignoreUFCache = args.length > 2 && args[1].toLowerCase(Locale.ENGLISH).contains("ignore"); - EssentialsUpgrade.uuidFileConvert(ess, ignoreUFCache); - - sender.sendMessage("UUID conversion complete. Check your server log for more information."); - } - - // Looks up various UUIDs for a user. - private void runUUIDTest(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { - if (args.length < 2) { - throw new Exception("/ uuidtest "); - } - final String name = args[1]; - sender.sendMessage("Looking up UUID for " + name); - - UUID onlineUUID = null; - - for (final Player player : ess.getOnlinePlayers()) { - if (player.getName().equalsIgnoreCase(name)) { - onlineUUID = player.getUniqueId(); - break; - } - } - - final UUID essUUID = ess.getUserMap().getUser(name).getConfigUUID(); - - final org.bukkit.OfflinePlayer player = ess.getServer().getOfflinePlayer(name); - final UUID bukkituuid = player.getUniqueId(); - sender.sendMessage("Bukkit Lookup: " + bukkituuid.toString()); - - if (onlineUUID != null && onlineUUID != bukkituuid) { - sender.sendMessage("Online player: " + onlineUUID.toString()); - } - - if (essUUID != null && essUUID != bukkituuid) { - sender.sendMessage("Essentials config: " + essUUID.toString()); - } - - final UUID npcuuid = UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8)); - sender.sendMessage("NPC UUID: " + npcuuid.toString()); - - final UUID offlineuuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)); - sender.sendMessage("Offline Mode UUID: " + offlineuuid.toString()); - } - // Displays versions of EssentialsX and related plugins. private void runVersion(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { if (sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.version")) return; @@ -681,7 +625,6 @@ protected List getTabCompleteOptions(final Server server, final CommandS options.add("cleanup"); options.add("homes"); //options.add("uuidconvert"); - //options.add("uuidtest"); //options.add("nya"); //options.add("moo"); return options; @@ -694,7 +637,6 @@ protected List getTabCompleteOptions(final Server server, final CommandS } break; case "reset": - case "uuidtest": if (args.length == 2) { return getPlayers(server, sender); } @@ -713,11 +655,6 @@ protected List getTabCompleteOptions(final Server server, final CommandS return server.getWorlds().stream().map(World::getName).collect(Collectors.toList()); } break; - case "uuidconvert": - if (args.length == 2) { - return Lists.newArrayList("ignoreUFCache"); - } - break; case "dump": final List list = Lists.newArrayList("config", "kits", "log", "discord", "all"); for (String arg : args) { From d963b2536607f4b207298fcc7b32a90a8dbb12f7 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 14 Oct 2021 23:39:07 -0400 Subject: [PATCH 03/38] Remove legacy code from UserMap and UUIDMap --- .../java/com/earth2me/essentials/UUIDMap.java | 14 +-- .../java/com/earth2me/essentials/UserMap.java | 108 ++---------------- 2 files changed, 9 insertions(+), 113 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java b/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java index be24d8f9983..21d5a228ec0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java @@ -9,7 +9,6 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -45,7 +44,7 @@ public UUIDMap(final net.ess3.api.IEssentials ess) { writeScheduler.scheduleWithFixedDelay(writeTaskRunnable, 5, 5, TimeUnit.SECONDS); } - public void loadAllUsers(final ConcurrentSkipListMap names, final ConcurrentSkipListMap> history) { + public void loadAllUsers(final ConcurrentSkipListMap names) { try { if (!userList.exists()) { userList.createNewFile(); @@ -60,7 +59,6 @@ public void loadAllUsers(final ConcurrentSkipListMap names, final } names.clear(); - history.clear(); loading = true; try (final BufferedReader reader = new BufferedReader(new FileReader(userList))) { @@ -74,16 +72,6 @@ public void loadAllUsers(final ConcurrentSkipListMap names, final final String name = values[0]; final UUID uuid = UUID.fromString(values[1]); names.put(name, uuid); - if (!history.containsKey(uuid)) { - final ArrayList list = new ArrayList<>(); - list.add(name); - history.put(uuid, list); - } else { - final ArrayList list = history.get(uuid); - if (!list.contains(name)) { - list.add(name); - } - } } } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserMap.java b/Essentials/src/main/java/com/earth2me/essentials/UserMap.java index 35f5eefe614..189c9d1efa6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserMap.java @@ -13,12 +13,8 @@ import org.bukkit.entity.Player; import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentSkipListMap; @@ -28,12 +24,9 @@ import java.util.regex.Pattern; public class UserMap extends CacheLoader implements IConf { - private static boolean legacy = false; - private static Method getLegacy; private final transient IEssentials ess; private final transient ConcurrentSkipListSet keys = new ConcurrentSkipListSet<>(); private final transient ConcurrentSkipListMap names = new ConcurrentSkipListMap<>(); - private final transient ConcurrentSkipListMap> history = new ConcurrentSkipListMap<>(); private final UUIDMap uuidMap; private final transient Cache users; private final Pattern validUserPattern = Pattern.compile("^[a-zA-Z0-9_]{2,16}$"); @@ -44,34 +37,23 @@ public UserMap(final IEssentials ess) { super(); this.ess = ess; uuidMap = new UUIDMap(ess); - //RemovalListener remListener = new UserMapRemovalListener(); - //users = CacheBuilder.newBuilder().maximumSize(ess.getSettings().getMaxUserCacheCount()).softValues().removalListener(remListener).build(this); final CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); final int maxCount = ess.getSettings().getMaxUserCacheCount(); - try { - cacheBuilder.maximumSize(maxCount); - } catch (final NoSuchMethodError nsme) { - legacy = true; - legacyMaximumSize(cacheBuilder, maxCount); - } + cacheBuilder.maximumSize(maxCount); cacheBuilder.softValues(); - if (!legacy) { - users = cacheBuilder.build(this); - } else { - users = legacyBuild(cacheBuilder); - } + users = cacheBuilder.build(this); } private void loadAllUsersAsync(final IEssentials ess) { ess.runTaskAsynchronously(() -> { synchronized (users) { - final File userdir = new File(ess.getDataFolder(), "userdata"); - if (!userdir.exists()) { + final File userDir = new File(ess.getDataFolder(), "userdata"); + if (!userDir.exists()) { return; } keys.clear(); users.invalidateAll(); - for (final String string : userdir.list()) { + for (final String string : userDir.list()) { if (!string.endsWith(".yml")) { continue; } @@ -82,7 +64,7 @@ private void loadAllUsersAsync(final IEssentials ess) { //Ignore these users till they rejoin. } } - uuidMap.loadAllUsers(names, history); + uuidMap.loadAllUsers(names); } }); } @@ -125,11 +107,7 @@ public User getUser(final String name) { public User getUser(final UUID uuid) { try { - if (!legacy) { - return ((LoadingCache) users).get(uuid.toString()); - } else { - return legacyCacheGet(uuid); - } + return ((LoadingCache) users).get(uuid.toString()); } catch (final ExecutionException | UncheckedExecutionException ex) { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.WARNING, ex, () -> "Exception while getting user for " + uuid); @@ -270,29 +248,9 @@ protected ConcurrentSkipListMap getNames() { return names; } - protected ConcurrentSkipListMap> getHistory() { - return history; - } - - public List getUserHistory(final UUID uuid) { - return history.get(uuid); - } - public UUIDMap getUUIDMap() { return uuidMap; } - // class UserMapRemovalListener implements RemovalListener - // { - // @Override - // public void onRemoval(final RemovalNotification notification) - // { - // Object value = notification.getValue(); - // if (value != null) - // { - // ((User)value).cleanup(); - // } - // } - // } private File getUserFileFromID(final UUID uuid) { final File userFolder = new File(ess.getDataFolder(), "userdata"); @@ -315,9 +273,7 @@ public User getUserFromBukkit(String name) { return null; } final org.bukkit.OfflinePlayer offlinePlayer = ess.getServer().getOfflinePlayer(name); - if (offlinePlayer == null) { - return null; - } + final UUID uuid; try { uuid = offlinePlayer.getUniqueId(); @@ -332,52 +288,4 @@ public User getUserFromBukkit(String name) { return getUser(uuid); } } - - private User legacyCacheGet(final UUID uuid) { - if (getLegacy == null) { - final Class usersClass = users.getClass(); - for (final Method m : usersClass.getDeclaredMethods()) { - if (m.getName().equals("get")) { - getLegacy = m; - getLegacy.setAccessible(true); - break; - } - } - } - try { - return (User) getLegacy.invoke(users, uuid.toString()); - } catch (final IllegalAccessException | InvocationTargetException e) { - return null; - } - } - - private void legacyMaximumSize(final CacheBuilder builder, final int maxCount) { - try { - final Method maxSizeLegacy = builder.getClass().getDeclaredMethod("maximumSize", Integer.TYPE); - maxSizeLegacy.setAccessible(true); - maxSizeLegacy.invoke(builder, maxCount); - } catch (final NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - e.printStackTrace(); - } - } - - @SuppressWarnings("unchecked") - private Cache legacyBuild(final CacheBuilder builder) { - Method build = null; - for (final Method method : builder.getClass().getDeclaredMethods()) { - if (method.getName().equals("build")) { - build = method; - break; - } - } - Cache legacyUsers; - try { - assert build != null; - build.setAccessible(true); - legacyUsers = (Cache) build.invoke(builder, this); - } catch (final IllegalAccessException | InvocationTargetException e) { - legacyUsers = null; - } - return legacyUsers; - } } From 1cedfb3f28299f6cd1d8f72edf8a37c188cfddcc Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 14 Oct 2021 23:52:05 -0400 Subject: [PATCH 04/38] Start work on ModernUUIDCache --- .../userstorage/ModernUUIDCache.java | 108 ++++++++++++++++++ .../essentials/userstorage/ModernUserMap.java | 11 ++ 2 files changed, 119 insertions(+) create mode 100644 Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java create mode 100644 Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java new file mode 100644 index 00000000000..121b77a31ad --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -0,0 +1,108 @@ +package com.earth2me.essentials.userstorage; + +import com.google.common.io.Files; +import net.ess3.api.IEssentials; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; + +public class ModernUUIDCache { + private final IEssentials ess; + + /** + * We use a name to uuid map for offline caching due to the following senerio: + * * JRoy and mdcfeYT420 play on a server + * * mdcfeYT420 changes his name to mdcfe + * * mdcfe doesn't log in the server for 31 days + * * JRoy changes his name to mdcfeYT420 + * * mdcfeYT420 (previously JRoy) logs in the server + * * In a UUID->Name based map, multiple uuids now point to the same map + * preventing any command which allows for offline players to resolve a + * single uuid from a name. + * + * This map is baked by a file-based cache. If this cache is missing, a new + * one is populated by iterating over all files in the userdata folder and + * caching the {@code last-account-name} value. + */ + private final ConcurrentHashMap nameToUuidMap = new ConcurrentHashMap<>(); + + private final ScheduledExecutorService writeExecutor = Executors.newSingleThreadScheduledExecutor(); + private final AtomicBoolean pendingWrite = new AtomicBoolean(false); + private final File nameToUuidFile; + + public ModernUUIDCache(final IEssentials ess) { + this.ess = ess; + this.nameToUuidFile = new File(ess.getDataFolder(), "usermap.bin"); + loadCache(); + writeExecutor.scheduleWithFixedDelay(() -> { + if (!pendingWrite.compareAndSet(true, false)) { + return; + } + + saveCache(); + }, 5, 5, TimeUnit.SECONDS); + } + + private void loadCache() { + try { + if (!nameToUuidFile.exists()) { + if (!nameToUuidFile.createNewFile()) { + throw new RuntimeException("Error while creating usermap.bin"); + } + return; + } + + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "Loading UUID cache from disk..."); + } + + nameToUuidMap.clear(); + + try (final DataInputStream dis = new DataInputStream(new FileInputStream(nameToUuidFile))) { + nameToUuidMap.put(dis.readUTF(), new UUID(dis.readLong(), dis.readLong())); + } + } catch (IOException e) { + ess.getLogger().log(Level.SEVERE, "Error while loading UUID cache", e); + } + } + + private void saveCache() { + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "Saving UUID cache to disk..."); + } + + try { + final File tmpMap = File.createTempFile("usermap", ".tmp.bin", ess.getDataFolder()); + + try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpMap))) { + for (final Map.Entry entry : nameToUuidMap.entrySet()) { + dos.writeUTF(entry.getKey()); + final UUID uuid = entry.getValue(); + dos.writeLong(uuid.getMostSignificantBits()); + dos.writeLong(uuid.getLeastSignificantBits()); + } + } + //noinspection UnstableApiUsage + Files.move(tmpMap, nameToUuidFile); + } catch (IOException e) { + ess.getLogger().log(Level.SEVERE, "Error while saving UUID cache", e); + } + } + + public void shutdown() { + writeExecutor.submit(this::saveCache); + writeExecutor.shutdown(); + } +} diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java new file mode 100644 index 00000000000..5cd86d0e2b4 --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -0,0 +1,11 @@ +package com.earth2me.essentials.userstorage; + +import net.ess3.api.IEssentials; + +public class ModernUserMap { + private final transient IEssentials ess; + + public ModernUserMap(final IEssentials ess) { + this.ess = ess; + } +} From 5887d7ad2ccaaa9bc17f875025f5c69a6f4a9351 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Fri, 15 Oct 2021 21:09:47 -0400 Subject: [PATCH 05/38] Also cache all uuids in seperate file --- .../userstorage/ModernUUIDCache.java | 70 ++++++++++++++++--- .../earth2me/essentials/OfflinePlayer.java | 2 +- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 121b77a31ad..09b19d86127 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -22,7 +23,7 @@ public class ModernUUIDCache { private final IEssentials ess; /** - * We use a name to uuid map for offline caching due to the following senerio: + * We use a name to uuid map for offline caching due to the following scenario: * * JRoy and mdcfeYT420 play on a server * * mdcfeYT420 changes his name to mdcfe * * mdcfe doesn't log in the server for 31 days @@ -37,21 +38,27 @@ public class ModernUUIDCache { * caching the {@code last-account-name} value. */ private final ConcurrentHashMap nameToUuidMap = new ConcurrentHashMap<>(); + private final CopyOnWriteArraySet uuidCache = new CopyOnWriteArraySet<>(); private final ScheduledExecutorService writeExecutor = Executors.newSingleThreadScheduledExecutor(); - private final AtomicBoolean pendingWrite = new AtomicBoolean(false); + private final AtomicBoolean pendingNameWrite = new AtomicBoolean(false); + private final AtomicBoolean pendingUuidWrite = new AtomicBoolean(false); private final File nameToUuidFile; + private final File uuidCacheFile; public ModernUUIDCache(final IEssentials ess) { this.ess = ess; this.nameToUuidFile = new File(ess.getDataFolder(), "usermap.bin"); + this.uuidCacheFile = new File(ess.getDataFolder(), "uuids.bin"); loadCache(); writeExecutor.scheduleWithFixedDelay(() -> { - if (!pendingWrite.compareAndSet(true, false)) { - return; + if (pendingNameWrite.compareAndSet(true, false)) { + saveNameToUuidCache(); } - saveCache(); + if (pendingUuidWrite.compareAndSet(true, false)) { + saveUuidCache(); + } }, 5, 5, TimeUnit.SECONDS); } @@ -65,7 +72,7 @@ private void loadCache() { } if (ess.getSettings().isDebug()) { - ess.getLogger().log(Level.INFO, "Loading UUID cache from disk..."); + ess.getLogger().log(Level.INFO, "Loading Name->UUID cache from disk..."); } nameToUuidMap.clear(); @@ -73,16 +80,58 @@ private void loadCache() { try (final DataInputStream dis = new DataInputStream(new FileInputStream(nameToUuidFile))) { nameToUuidMap.put(dis.readUTF(), new UUID(dis.readLong(), dis.readLong())); } + } catch (IOException e) { + ess.getLogger().log(Level.SEVERE, "Error while loading Name->UUID cache", e); + } + + try { + if (!uuidCacheFile.exists()) { + if (!uuidCacheFile.createNewFile()) { + throw new RuntimeException("Error while creating uuids.bin"); + } + return; + } + + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "Loading UUID cache from disk..."); + } + + uuidCache.clear(); + + try (final DataInputStream dis = new DataInputStream(new FileInputStream(uuidCacheFile))) { + uuidCache.add(new UUID(dis.readLong(), dis.readLong())); + } } catch (IOException e) { ess.getLogger().log(Level.SEVERE, "Error while loading UUID cache", e); } } - private void saveCache() { + private void saveUuidCache() { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.INFO, "Saving UUID cache to disk..."); } + try { + final File tmpMap = File.createTempFile("uuids", ".tmp.bin", ess.getDataFolder()); + + try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpMap))) { + for (final UUID uuid: uuidCache) { + dos.writeLong(uuid.getMostSignificantBits()); + dos.writeLong(uuid.getLeastSignificantBits()); + } + } + //noinspection UnstableApiUsage + Files.move(tmpMap, uuidCacheFile); + } catch (IOException e) { + ess.getLogger().log(Level.SEVERE, "Error while saving UUID cache", e); + } + } + + private void saveNameToUuidCache() { + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "Saving Name->UUID cache to disk..."); + } + try { final File tmpMap = File.createTempFile("usermap", ".tmp.bin", ess.getDataFolder()); @@ -97,12 +146,15 @@ private void saveCache() { //noinspection UnstableApiUsage Files.move(tmpMap, nameToUuidFile); } catch (IOException e) { - ess.getLogger().log(Level.SEVERE, "Error while saving UUID cache", e); + ess.getLogger().log(Level.SEVERE, "Error while saving Name->UUID cache", e); } } public void shutdown() { - writeExecutor.submit(this::saveCache); + writeExecutor.submit(() -> { + saveNameToUuidCache(); + saveUuidCache(); + }); writeExecutor.shutdown(); } } diff --git a/providers/1_8Provider/src/main/java/com/earth2me/essentials/OfflinePlayer.java b/providers/1_8Provider/src/main/java/com/earth2me/essentials/OfflinePlayer.java index ab840a662a0..99772592a2c 100644 --- a/providers/1_8Provider/src/main/java/com/earth2me/essentials/OfflinePlayer.java +++ b/providers/1_8Provider/src/main/java/com/earth2me/essentials/OfflinePlayer.java @@ -1341,7 +1341,7 @@ public String getName() { return name; } - void setName(final String name) { + public void setName(final String name) { this.name = base.getName(); if (this.name == null) { this.name = name; From ea2b071372ae496db6bcc1a853618bd2584d746b Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 12:27:19 -0400 Subject: [PATCH 06/38] Complete first iteration of ModernUserMap --- .../com/earth2me/essentials/Essentials.java | 17 ++- .../com/earth2me/essentials/IEssentials.java | 4 + .../java/com/earth2me/essentials/User.java | 3 +- .../com/earth2me/essentials/UserData.java | 18 +-- .../java/com/earth2me/essentials/UserMap.java | 4 - .../userstorage/ModernUUIDCache.java | 38 ++++++ .../essentials/userstorage/ModernUserMap.java | 123 +++++++++++++++++- 7 files changed, 183 insertions(+), 24 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index ba26a7246d5..0eed52ac5fc 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -39,6 +39,7 @@ import com.earth2me.essentials.textreader.KeywordReplacer; import com.earth2me.essentials.textreader.SimpleTextInput; import com.earth2me.essentials.updatecheck.UpdateChecker; +import com.earth2me.essentials.userstorage.ModernUserMap; import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.VersionUtil; import io.papermc.lib.PaperLib; @@ -145,7 +146,8 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient CustomItemResolver customItemResolver; private transient PermissionsHandler permissionsHandler; private transient AlternativeCommandsHandler alternativeCommandsHandler; - private transient UserMap userMap; + private transient UserMap legacyUserMap; + private transient ModernUserMap userMap; private transient BalanceTopImpl balanceTop; private transient ExecuteTimer execTimer; private transient MailService mail; @@ -208,7 +210,7 @@ public void setupForTesting(final Server server) throws IOException, InvalidDesc LOGGER.log(Level.INFO, dataFolder.toString()); settings = new Settings(this); mail = new MailServiceImpl(this); - userMap = new UserMap(this); + userMap = new ModernUserMap(this); balanceTop = new BalanceTopImpl(this); permissionsHandler = new PermissionsHandler(this, false); Economy.setEss(this); @@ -284,8 +286,7 @@ public void onEnable() { mail = new MailServiceImpl(this); execTimer.mark("Init(Mail)"); - userMap = new UserMap(this); - confList.add(userMap); + userMap = new ModernUserMap(this); execTimer.mark("Init(Usermap)"); balanceTop = new BalanceTopImpl(this); @@ -541,7 +542,7 @@ public void onDisable() { Economy.setEss(null); Trade.closeLog(); - getUserMap().getUUIDMap().shutdown(); + getUsers().shutdown(); HandlerList.unregisterAll(this); } @@ -1152,7 +1153,13 @@ public IItemDb getItemDb() { } @Override + @Deprecated public UserMap getUserMap() { + return legacyUserMap; + } + + @Override + public ModernUserMap getUsers() { return userMap; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index 73e09b8c7a7..189bbcedac9 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -7,6 +7,7 @@ import com.earth2me.essentials.commands.PlayerNotFoundException; import com.earth2me.essentials.perm.PermissionsHandler; import com.earth2me.essentials.updatecheck.UpdateChecker; +import com.earth2me.essentials.userstorage.ModernUserMap; import net.ess3.nms.refl.providers.ReflOnlineModeProvider; import net.ess3.provider.ContainerProvider; import net.ess3.provider.FormattedCommandAliasProvider; @@ -116,6 +117,9 @@ public interface IEssentials extends Plugin { IItemDb getItemDb(); + ModernUserMap getUsers(); + + @Deprecated UserMap getUserMap(); BalanceTop getBalanceTop(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 9d8140cddba..aabc9e950da 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -90,9 +90,8 @@ public User(final Player base, final IEssentials ess) { this.messageRecipient = new SimpleMessageRecipient(ess, this); } - User update(final Player base) { + public void update(final Player base) { setBase(base); - return this; } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserData.java b/Essentials/src/main/java/com/earth2me/essentials/UserData.java index 08a2b65f630..6525072032a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserData.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserData.java @@ -43,19 +43,11 @@ protected UserData(final Player base, final IEssentials ess) { super(base); this.ess = ess; final File folder = new File(ess.getDataFolder(), "userdata"); - if (!folder.exists()) { - folder.mkdirs(); + if (!folder.exists() && !folder.mkdirs()) { + throw new RuntimeException("Unable to create userdata folder!"); } - String filename; - try { - filename = base.getUniqueId().toString(); - } catch (final Throwable ex) { - ess.getLogger().warning("Falling back to old username system for " + base.getName()); - filename = base.getName(); - } - - config = new EssentialsUserConfiguration(base.getName(), base.getUniqueId(), new File(folder, filename + ".yml")); + config = new EssentialsUserConfiguration(base.getName(), base.getUniqueId(), new File(folder, base.getUniqueId() + ".yml")); reloadConfig(); if (config.getUsername() == null) { @@ -65,7 +57,9 @@ protected UserData(final Player base, final IEssentials ess) { public final void reset() { config.blockingSave(); - config.getFile().delete(); + if (!config.getFile().delete()) { + ess.getLogger().warning("Unable to delete data file for " + config.getFile().getName()); + } if (config.getUsername() != null) { ess.getUserMap().removeUser(config.getUsername()); if (isNPC()) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserMap.java b/Essentials/src/main/java/com/earth2me/essentials/UserMap.java index 189c9d1efa6..06db871a7d4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserMap.java @@ -69,10 +69,6 @@ private void loadAllUsersAsync(final IEssentials ess) { }); } - public boolean userExists(final UUID uuid) { - return keys.contains(uuid); - } - public User getUser(final String name) { final String sanitizedName = StringUtil.safeString(name); try { diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 09b19d86127..86a6d6c7e67 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -1,5 +1,6 @@ package com.earth2me.essentials.userstorage; +import com.earth2me.essentials.utils.StringUtil; import com.google.common.io.Files; import net.ess3.api.IEssentials; @@ -9,7 +10,9 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; @@ -62,6 +65,34 @@ public ModernUUIDCache(final IEssentials ess) { }, 5, 5, TimeUnit.SECONDS); } + public UUID getCachedUUID(final String name) { + return nameToUuidMap.get(getSanitizedName(name)); + } + + public Set getCachedUUIDs() { + return Collections.unmodifiableSet(uuidCache); + } + + public int getCacheSize() { + return uuidCache.size(); + } + + public String getSanitizedName(final String name) { + return ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name; + } + + public void updateCache(final UUID uuid, final String name) { + if (uuidCache.add(uuid)) { + pendingUuidWrite.set(true); + } + if (name != null) { + final UUID replacedUuid = nameToUuidMap.put(getSanitizedName(name), uuid); + if (uuid.equals(replacedUuid)) { + pendingNameWrite.set(true); + } + } + } + private void loadCache() { try { if (!nameToUuidFile.exists()) { @@ -155,6 +186,13 @@ public void shutdown() { saveNameToUuidCache(); saveUuidCache(); }); + try { + if (!writeExecutor.awaitTermination(30, TimeUnit.SECONDS)) { + ess.getLogger().log(Level.SEVERE, "UUID cache took too long to save!"); + } + } catch (InterruptedException e) { + ess.getLogger().log(Level.SEVERE, "Error while shutting down UUID cache", e); + } writeExecutor.shutdown(); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index 5cd86d0e2b4..b827377dd5f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -1,11 +1,132 @@ package com.earth2me.essentials.userstorage; +import com.earth2me.essentials.OfflinePlayer; +import com.earth2me.essentials.User; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import net.ess3.api.IEssentials; +import org.bukkit.entity.Player; -public class ModernUserMap { +import java.io.File; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; + +public class ModernUserMap extends CacheLoader { private final transient IEssentials ess; + private final transient ModernUUIDCache uuidCache; + private final transient LoadingCache userCache; public ModernUserMap(final IEssentials ess) { this.ess = ess; + this.uuidCache = new ModernUUIDCache(ess); + this.userCache = CacheBuilder.newBuilder() + .maximumSize(ess.getSettings().getMaxUserCacheCount()) + .softValues() + .build(this); + } + + public Set getAllUserUUIDs() { + return uuidCache.getCachedUUIDs(); + } + + public int getUserCount() { + return uuidCache.getCacheSize(); + } + + public User getUser(final UUID uuid) { + if (uuid == null) { + return null; + } + + try { + return userCache.get(uuid); + } catch (ExecutionException e) { + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.WARNING, "Exception while getting user for " + uuid, e); + } + return null; + } + } + + public User getUser(final Player base) { + if (base == null) { + return null; + } + + User user = getUser(base.getUniqueId()); + if (user == null) { + ess.getLogger().log(Level.INFO, "Essentials created a User for " + base.getName() + " (" + base.getUniqueId() + ") for non Bukkit type: " + base.getClass().getName()); + user = new User(base, ess); + } else if (!base.equals(user.getBase())) { + ess.getLogger().log(Level.INFO, "Essentials updated the underlying Player object for " + user.getUUID()); + user.update(base); + } + return user; + } + + public User getUser(final String name) { + if (name == null) { + return null; + } + + final User user = getUser(uuidCache.getCachedUUID(name)); + if (user != null && user.getBase() instanceof OfflinePlayer) { + if (user.getLastAccountName() != null) { + ((OfflinePlayer) user.getBase()).setName(user.getLastAccountName()); + } else { + ((OfflinePlayer) user.getBase()).setName(name); + } + } + return user; + } + + @Override + public User load(final UUID uuid) throws Exception { + final User user = loadUncachedUser(uuid); + if (user != null) { + return user; + } + + throw new Exception("User not found!"); + } + + /** + * Gets a User by the given UUID in the cache, if present, otherwise loads the user without placing them in the cache. + * Ideally to be used when running operations on all stored. + */ + public User loadUncachedUser(final UUID uuid) { + User user = userCache.getIfPresent(uuid); + if (user != null) { + return user; + } + + Player player = ess.getServer().getPlayer(uuid); + if (player != null) { + user = new User(player, ess); + uuidCache.updateCache(uuid, player.getName()); + return user; + } + + final File userFile = getUserFile(uuid); + if (userFile.exists()) { + player = new OfflinePlayer(uuid, ess.getServer()); + user = new User(player, ess); + ((OfflinePlayer) player).setName(user.getLastAccountName()); + uuidCache.updateCache(uuid, null); + return user; + } + + return null; + } + + private File getUserFile(final UUID uuid) { + return new File(new File(ess.getDataFolder(), "userdata"), uuid.toString() + ".yml"); + } + + public void shutdown() { + uuidCache.shutdown(); } } From 3d6962d0489818852593c54284ea5e998251cae6 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 13:11:54 -0400 Subject: [PATCH 07/38] Replace uuid cache upgrade --- .../com/earth2me/essentials/Essentials.java | 5 +- .../essentials/EssentialsUpgrade.java | 95 +++++++++---------- .../userstorage/ModernUUIDCache.java | 36 ++++--- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 0eed52ac5fc..c187fdfce36 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -283,6 +283,9 @@ public void onEnable() { confList.add(settings); execTimer.mark("Settings"); + upgrade.preModules(); + execTimer.mark("Upgrade2"); + mail = new MailServiceImpl(this); execTimer.mark("Init(Mail)"); @@ -298,7 +301,7 @@ public void onEnable() { execTimer.mark("Kits"); upgrade.afterSettings(); - execTimer.mark("Upgrade2"); + execTimer.mark("Upgrade3"); warps = new Warps(this.getDataFolder()); confList.add(warps); diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index 5511967cf5d..a21c203babf 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -6,7 +6,6 @@ import com.earth2me.essentials.craftbukkit.BanLookup; import com.earth2me.essentials.utils.StringUtil; import com.google.common.base.Charsets; -import com.google.common.collect.Maps; import com.google.common.io.Files; import com.google.gson.reflect.TypeToken; import net.ess3.api.IEssentials; @@ -31,10 +30,10 @@ import java.math.BigInteger; import java.security.DigestInputStream; import java.security.MessageDigest; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -48,7 +47,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.earth2me.essentials.I18n.tl; @@ -915,15 +913,17 @@ private void updateBan(final String playerName, final String banReason, final Lo Bukkit.getBanList(BanList.Type.NAME).addBan(playerName, banReason, banTimeout == 0 ? null : new Date(banTimeout), Console.NAME); } - private void repairUserMap() { - if (doneFile.getBoolean("userMapRepaired", false)) { + public void generateUidCache() { + if (doneFile.getBoolean("newUidCacheGenerated", false)) { return; } - ess.getLogger().info("Starting usermap repair"); final File userdataFolder = new File(ess.getDataFolder(), "userdata"); - if (!userdataFolder.isDirectory()) { + + if (!userdataFolder.isDirectory() || usermapFile.exists() || uidsFile.exists()) { ess.getLogger().warning("Missing userdata folder, aborting"); + doneFile.setProperty("newUidCacheGenerated", true); + doneFile.save(); return; } final File[] files = userdataFolder.listFiles(YML_FILTER); @@ -931,58 +931,48 @@ private void repairUserMap() { final DecimalFormat format = new DecimalFormat("#0.00"); final Map names = Maps.newHashMap(); - for (int index = 0; index < files.length; index++) { - final File file = files[index]; - try { - UUID uuid = null; - final String filename = file.getName(); - final String configData = new String(java.nio.file.Files.readAllBytes(file.toPath()), Charsets.UTF_8); + try { + if (!usermapFile.createNewFile() || !uidsFile.createNewFile()) { + ess.getLogger().warning("Couldn't create usermap.bin or uuids.bin, aborting"); + return; + } - if (filename.length() > 36) { - try { - // ".yml" ending has 4 chars... - uuid = UUID.fromString(filename.substring(0, filename.length() - 4)); - } catch (final IllegalArgumentException ignored) { - } - } + final Set uuids = new HashSet<>(); + final Map nameToUuidMap = new HashMap<>(); - final Matcher uuidMatcher = PATTERN_CONFIG_UUID.matcher(configData); - if (uuidMatcher.find()) { + final File[] files = userdataFolder.listFiles(YML_FILTER); + if (files != null) { + for (final File file : files) { try { - uuid = UUID.fromString(uuidMatcher.group(1)); - } catch (final IllegalArgumentException ignored) { - } - } - - if (uuid == null) { - // Don't import - continue; - } - - final Matcher nameMatcher = PATTERN_CONFIG_NAME.matcher(configData); - if (nameMatcher.find()) { - final String username = nameMatcher.group(1); - if (username != null && username.length() > 0) { - names.put(StringUtil.safeString(username), uuid); + final String fileName = file.getName(); + final UUID uuid = UUID.fromString(fileName.substring(0, fileName.length() - 4)); + final EssentialsConfiguration config = new EssentialsConfiguration(file); + config.load(); + String name = config.getString("last-account-name", null); + name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name; + + uuids.add(uuid); + if (name != null) { + nameToUuidMap.put(name, uuid); + } + } catch (IllegalArgumentException | IndexOutOfBoundsException ignored) { } } + } - if (index % 1000 == 0) { - ess.getLogger().info("Reading: " + format.format((100d * (double) index) / files.length) - + "%"); - } - } catch (final IOException e) { - ess.getLogger().log(Level.SEVERE, "Error while reading file: ", e); - return; + if (!nameToUuidMap.isEmpty()) { + ModernUUIDCache.writeNameUuidMap(usermapFile, nameToUuidMap); } - } - ess.getUserMap().getNames().putAll(names); - ess.getUserMap().reloadConfig(); + if (!uuids.isEmpty()) { + ModernUUIDCache.writeUuidCache(uidsFile, uuids); + } - doneFile.setProperty("userMapRepaired", true); - doneFile.save(); - ess.getLogger().info("Completed usermap repair."); + doneFile.setProperty("newUidCacheGenerated", true); + doneFile.save(); + } catch (final IOException e) { + ess.getLogger().log(Level.SEVERE, "Error while generating initial uuids/names cache", e); + } } public void beforeSettings() { @@ -993,6 +983,10 @@ public void beforeSettings() { moveMotdRulesToFile("rules"); } + public void preModules() { + generateUidCache(); + } + public void afterSettings() { sanitizeAllUserFilenames(); updateUsersPowerToolsFormat(); @@ -1003,7 +997,6 @@ public void afterSettings() { uuidFileChange(); banFormatChange(); warnMetrics(); - repairUserMap(); convertIgnoreList(); convertStupidCamelCaseUserdataKeys(); convertMailList(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 86a6d6c7e67..4eec5f85482 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -145,12 +145,7 @@ private void saveUuidCache() { try { final File tmpMap = File.createTempFile("uuids", ".tmp.bin", ess.getDataFolder()); - try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpMap))) { - for (final UUID uuid: uuidCache) { - dos.writeLong(uuid.getMostSignificantBits()); - dos.writeLong(uuid.getLeastSignificantBits()); - } - } + writeUuidCache(tmpMap, uuidCache); //noinspection UnstableApiUsage Files.move(tmpMap, uuidCacheFile); } catch (IOException e) { @@ -166,14 +161,7 @@ private void saveNameToUuidCache() { try { final File tmpMap = File.createTempFile("usermap", ".tmp.bin", ess.getDataFolder()); - try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpMap))) { - for (final Map.Entry entry : nameToUuidMap.entrySet()) { - dos.writeUTF(entry.getKey()); - final UUID uuid = entry.getValue(); - dos.writeLong(uuid.getMostSignificantBits()); - dos.writeLong(uuid.getLeastSignificantBits()); - } - } + writeNameUuidMap(tmpMap, nameToUuidMap); //noinspection UnstableApiUsage Files.move(tmpMap, nameToUuidFile); } catch (IOException e) { @@ -181,6 +169,26 @@ private void saveNameToUuidCache() { } } + public static void writeUuidCache(final File file, Set uuids) throws IOException { + try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) { + for (final UUID uuid: uuids) { + dos.writeLong(uuid.getMostSignificantBits()); + dos.writeLong(uuid.getLeastSignificantBits()); + } + } + } + + public static void writeNameUuidMap(File file, Map nameToUuidMap) throws IOException { + try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) { + for (final Map.Entry entry : nameToUuidMap.entrySet()) { + dos.writeUTF(entry.getKey()); + final UUID uuid = entry.getValue(); + dos.writeLong(uuid.getMostSignificantBits()); + dos.writeLong(uuid.getLeastSignificantBits()); + } + } + } + public void shutdown() { writeExecutor.submit(() -> { saveNameToUuidCache(); From 1244f500810c9c0e3bc3c22966a1b6489888ef2b Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 14:59:06 -0400 Subject: [PATCH 08/38] Replace all usages of UserMap with ModernUserMap --- .../earth2me/essentials/BalanceTopImpl.java | 4 +-- .../essentials/EssentialsPlayerListener.java | 2 +- .../essentials/EssentialsUpgrade.java | 19 +++++++------ .../com/earth2me/essentials/UserData.java | 7 ++--- .../com/earth2me/essentials/api/Economy.java | 7 +++-- .../commands/Commandbalancetop.java | 4 +-- .../commands/Commandessentials.java | 15 ++++------ .../essentials/commands/Commandmail.java | 4 +-- .../essentials/commands/Commandseen.java | 9 ++---- .../commands/EssentialsLoopCommand.java | 6 ++-- .../config/EssentialsUserConfiguration.java | 2 +- .../economy/vault/VaultEconomyProvider.java | 28 +++++++++++-------- .../textreader/KeywordReplacer.java | 2 +- .../userstorage/ModernUUIDCache.java | 28 ++++++++++++++++++- .../essentials/userstorage/ModernUserMap.java | 7 +++++ .../discord/listeners/BukkitListener.java | 2 +- 16 files changed, 90 insertions(+), 56 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/BalanceTopImpl.java b/Essentials/src/main/java/com/earth2me/essentials/BalanceTopImpl.java index 07787715e0f..2c65ef7ed2b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/BalanceTopImpl.java +++ b/Essentials/src/main/java/com/earth2me/essentials/BalanceTopImpl.java @@ -28,8 +28,8 @@ public BalanceTopImpl(IEssentials ess) { private void calculateBalanceTopMap() { final List entries = new LinkedList<>(); BigDecimal newTotal = BigDecimal.ZERO; - for (UUID u : ess.getUserMap().getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(u); + for (UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); if (user != null) { if (!ess.getSettings().isNpcsInBalanceRanking() && user.isNPC()) { // Don't list NPCs in output diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index b5aa2e64d7a..293781c21c4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -363,7 +363,7 @@ public void run() { } else if (ess.getSettings().isCustomJoinMessage()) { final String msg = (newUsername ? ess.getSettings().getCustomNewUsernameMessage() : ess.getSettings().getCustomJoinMessage()) .replace("{PLAYER}", player.getDisplayName()).replace("{USERNAME}", player.getName()) - .replace("{UNIQUE}", NumberFormat.getInstance().format(ess.getUserMap().getUniqueUsers())) + .replace("{UNIQUE}", NumberFormat.getInstance().format(ess.getUsers().getUserCount())) .replace("{ONLINE}", NumberFormat.getInstance().format(ess.getOnlinePlayers().size())) .replace("{UPTIME}", DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime())) .replace("{PREFIX}", FormatUtil.replaceFormat(ess.getPermissionsHandler().getPrefix(player))) diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index a21c203babf..8b2ac706f85 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -4,6 +4,7 @@ import com.earth2me.essentials.config.EssentialsConfiguration; import com.earth2me.essentials.config.EssentialsUserConfiguration; import com.earth2me.essentials.craftbukkit.BanLookup; +import com.earth2me.essentials.userstorage.ModernUUIDCache; import com.earth2me.essentials.utils.StringUtil; import com.google.common.base.Charsets; import com.google.common.io.Files; @@ -93,7 +94,6 @@ public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUF final int showProgress = countFiles % 250; if (showProgress == 0) { - ess.getUserMap().getUUIDMap().forceWriteUUIDMap(); ess.getLogger().info("Converted " + countFiles + "/" + userdir.list().length); } @@ -103,7 +103,8 @@ public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUF final EssentialsUserConfiguration config; UUID uuid = null; try { - uuid = UUID.fromString(name); + //noinspection ResultOfMethodCallIgnored + UUID.fromString(name); } catch (final IllegalArgumentException ex) { final File file = new File(userdir, string); final EssentialsConfiguration conf = new EssentialsConfiguration(file); @@ -115,6 +116,7 @@ public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUF final String uuidString = conf.getString(uuidConf, null); + //noinspection ConstantConditions for (int i = 0; i < 4; i++) { try { uuid = UUID.fromString(uuidString); @@ -122,7 +124,7 @@ public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUF break; } catch (final Exception ex2) { if (conf.getBoolean("npc", false)) { - uuid = UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8)); + uuid = UUID.nameUUIDFromBytes(("NPC:" + (ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name)).getBytes(Charsets.UTF_8)); break; } @@ -130,23 +132,24 @@ public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUF uuid = player.getUniqueId(); } + //noinspection ConstantConditions if (uuid != null) { countBukkit++; break; } } + //noinspection ConstantConditions if (uuid != null) { conf.blockingSave(); config = new EssentialsUserConfiguration(name, uuid, new File(userdir, uuid + ".yml")); config.convertLegacyFile(); - ess.getUserMap().trackUUID(uuid, name, false); + ess.getUsers().loadUncachedUser(uuid); continue; } countFails++; } } - ess.getUserMap().getUUIDMap().forceWriteUUIDMap(); ess.getLogger().info("Converted " + countFiles + "/" + countFiles + ". Conversion complete."); ess.getLogger().info("Converted via cache: " + countEssCache + " :: Converted via lookup: " + countBukkit + " :: Failed to convert: " + countFails); @@ -918,6 +921,8 @@ public void generateUidCache() { return; } + final File usermapFile = new File(ess.getDataFolder(), "usermap.bin"); + final File uidsFile = new File(ess.getDataFolder(), "uuids.bin"); final File userdataFolder = new File(ess.getDataFolder(), "userdata"); if (!userdataFolder.isDirectory() || usermapFile.exists() || uidsFile.exists()) { @@ -926,10 +931,6 @@ public void generateUidCache() { doneFile.save(); return; } - final File[] files = userdataFolder.listFiles(YML_FILTER); - - final DecimalFormat format = new DecimalFormat("#0.00"); - final Map names = Maps.newHashMap(); try { if (!usermapFile.createNewFile() || !uidsFile.createNewFile()) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserData.java b/Essentials/src/main/java/com/earth2me/essentials/UserData.java index 6525072032a..d9cf1bf0864 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserData.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserData.java @@ -61,10 +61,10 @@ public final void reset() { ess.getLogger().warning("Unable to delete data file for " + config.getFile().getName()); } if (config.getUsername() != null) { - ess.getUserMap().removeUser(config.getUsername()); + ess.getUsers().invalidate(config.getUuid()); if (isNPC()) { - final String uuid = UUID.nameUUIDFromBytes(("NPC:" + StringUtil.safeString(config.getUsername())).getBytes(Charsets.UTF_8)).toString(); - ess.getUserMap().removeUserUUID(uuid); + final String name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(config.getUsername()) : config.getUsername(); + ess.getUsers().invalidate(UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8))); } } } @@ -590,7 +590,6 @@ public void setLastAccountName(final String lastAccountName) { } holder.lastAccountName(lastAccountName); config.save(); - ess.getUserMap().trackUUID(getConfigUUID(), lastAccountName, true); } public boolean arePowerToolsEnabled() { diff --git a/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java b/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java index dd7ae19f71c..5ef09f8d700 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java +++ b/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java @@ -45,7 +45,7 @@ public static void setEss(final IEssentials aEss) { private static void createNPCFile(String name) { final File folder = new File(ess.getDataFolder(), "userdata"); - name = StringUtil.safeString(name); + name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name; if (!folder.exists()) { if (!folder.mkdirs()) { throw new RuntimeException("Error while creating userdata directory!"); @@ -63,7 +63,8 @@ private static void createNPCFile(String name) { npcConfig.setProperty("last-account-name", name); npcConfig.setProperty("money", ess.getSettings().getStartingBalance()); npcConfig.blockingSave(); - ess.getUserMap().trackUUID(npcUUID, name, false); + // This will load the NPC into the UserMap + UUID cache + ess.getUsers().getUser(npcUUID); } private static void deleteNPC(final String name) { @@ -96,7 +97,7 @@ private static User getUserByName(final String name) { } if (user == null) { - user = getUserByUUID(UUID.nameUUIDFromBytes(("NPC:" + StringUtil.safeString(name)).getBytes(Charsets.UTF_8))); + user = getUserByUUID(UUID.nameUUIDFromBytes(("NPC:" + (ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name)).getBytes(Charsets.UTF_8))); } return user; diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java index 0aa0ce63986..a31a54a181e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java @@ -56,8 +56,8 @@ protected void run(final Server server, final CommandSource sender, final String } // If there are less than 50 users in our usermap, there is no need to display a warning as these calculations should be done quickly - if (ess.getUserMap().getUniqueUsers() > MINUSERS) { - sender.sendMessage(tl("orderBalances", ess.getUserMap().getUniqueUsers())); + if (ess.getUsers().getUserCount() > MINUSERS) { + sender.sendMessage(tl("orderBalances", ess.getUsers().getUserCount())); } ess.runTaskAsynchronously(new Viewer(sender, page, force)); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 3a1e05788e7..92dcf4c8a7c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -2,7 +2,6 @@ import com.earth2me.essentials.CommandSource; import com.earth2me.essentials.User; -import com.earth2me.essentials.UserMap; import com.earth2me.essentials.economy.EconomyLayer; import com.earth2me.essentials.economy.EconomyLayers; import com.earth2me.essentials.utils.DateUtil; @@ -399,12 +398,11 @@ private void runCleanup(final Server server, final CommandSource sender, final S final long daysArg = Long.parseLong(args[1]); final double moneyArg = args.length >= 3 ? FloatUtil.parseDouble(args[2].replaceAll("[^0-9\\.]", "")) : 0; final int homesArg = args.length >= 4 && NumberUtil.isInt(args[3]) ? Integer.parseInt(args[3]) : 0; - final UserMap userMap = ess.getUserMap(); ess.runTaskAsynchronously(() -> { final long currTime = System.currentTimeMillis(); - for (final UUID u : userMap.getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(u); + for (final UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); if (user == null) { continue; } @@ -449,13 +447,12 @@ private void runHomes(final Server server, final CommandSource sender, final Str throw new Exception(HOMES_USAGE); } - final UserMap userMap = ess.getUserMap(); switch (args[1]) { case "fix": sender.sendMessage(tl("fixingHomes")); ess.runTaskAsynchronously(() -> { - for (final UUID u : userMap.getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(u); + for (final UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); if (user == null) { continue; } @@ -479,8 +476,8 @@ private void runHomes(final Server server, final CommandSource sender, final Str } sender.sendMessage(filterByWorld ? tl("deletingHomesWorld", args[2]) : tl("deletingHomes")); ess.runTaskAsynchronously(() -> { - for (final UUID u : userMap.getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(u); + for (final UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); if (user == null) { continue; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java index 387ec86662d..97a12975bd0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java @@ -242,8 +242,8 @@ private class SendAll implements Runnable { @Override public void run() { - for (UUID userid : ess.getUserMap().getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(userid); + for (final UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); if (user != null) { user.sendMail(messageRecipient, message); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java index 84d58e4f8ef..e03d836759b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java @@ -2,7 +2,6 @@ import com.earth2me.essentials.CommandSource; import com.earth2me.essentials.User; -import com.earth2me.essentials.UserMap; import com.earth2me.essentials.craftbukkit.BanLookup; import com.earth2me.essentials.utils.DateUtil; import com.earth2me.essentials.utils.FormatUtil; @@ -63,7 +62,7 @@ protected void run(final Server server, final CommandSource sender, final String ess.getScheduler().runTaskAsynchronously(ess, new Runnable() { @Override public void run() { - final User userFromBukkit = ess.getUserMap().getUserFromBukkit(args[0]); + final User userFromBukkit = ess.getUsers().getUser(args[0]); try { if (userFromBukkit != null) { showUserSeen(userFromBukkit); @@ -191,14 +190,12 @@ private void seenOffline(final CommandSource sender, final User user, final bool } private void seenIP(final CommandSource sender, final String ipAddress, final String display) { - final UserMap userMap = ess.getUserMap(); - sender.sendMessage(tl("runningPlayerMatch", display)); ess.runTaskAsynchronously(() -> { final List matches = new ArrayList<>(); - for (final UUID u : userMap.getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(u); + for (final UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); if (user == null) { continue; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsLoopCommand.java b/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsLoopCommand.java index 29934d7e895..3ac7f51a396 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsLoopCommand.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/EssentialsLoopCommand.java @@ -32,9 +32,9 @@ protected void loopOfflinePlayersConsumer(final Server server, final CommandSour final User matchedUser = ess.getUser(uuid); userConsumer.accept(matchedUser); } else if (matchWildcards && searchTerm.contentEquals("**")) { - for (final UUID sUser : ess.getUserMap().getAllUniqueUsers()) { - final User matchedUser = ess.getUser(sUser); - userConsumer.accept(matchedUser); + for (final UUID u : ess.getUsers().getAllUserUUIDs()) { + final User user = ess.getUsers().loadUncachedUser(u); + userConsumer.accept(user); } } else if (matchWildcards && searchTerm.contentEquals("*")) { final boolean skipHidden = sender.isPlayer() && !ess.getUser(sender.getPlayer()).canInteractVanished(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsUserConfiguration.java b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsUserConfiguration.java index 798b4da3720..590010a2807 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsUserConfiguration.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsUserConfiguration.java @@ -51,7 +51,7 @@ public void convertLegacyFile() { private File getAltFile() { final UUID fn = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username.toLowerCase(Locale.ENGLISH)).getBytes(Charsets.UTF_8)); - return new File(configFile.getParentFile(), fn.toString() + ".yml"); + return new File(configFile.getParentFile(), fn + ".yml"); } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java b/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java index 4ba9e66ca16..b7eb9a9099d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java +++ b/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java @@ -11,6 +11,7 @@ import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.EconomyResponse; import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; import java.io.File; import java.math.BigDecimal; @@ -81,7 +82,7 @@ public boolean hasAccount(String playerName) { return true; } // We may not have the player name in the usermap, let's double check an NPC account with this name doesn't exist. - return com.earth2me.essentials.api.Economy.playerExists(UUID.nameUUIDFromBytes(("NPC:" + StringUtil.safeString(playerName)).getBytes(Charsets.UTF_8))); + return com.earth2me.essentials.api.Economy.playerExists(UUID.nameUUIDFromBytes(("NPC:" + (ess.getSettings().isSafeUsermap() ? StringUtil.safeString(playerName) : playerName)).getBytes(Charsets.UTF_8))); } @Override @@ -310,21 +311,26 @@ public boolean createPlayerAccount(OfflinePlayer player) { npcConfig.setProperty("last-account-name", player.getName()); npcConfig.setProperty("money", ess.getSettings().getStartingBalance()); npcConfig.blockingSave(); - ess.getUserMap().trackUUID(player.getUniqueId(), player.getName(), false); + // This will load the NPC into the UserMap + UUID cache + ess.getUsers().getUser(player.getUniqueId()); return true; } // Loading a v4 UUID that we somehow didn't track, mark it as a normal player and hope for the best, vault sucks :/ - try { - if (ess.getSettings().isDebug()) { - LOGGER.info("Vault requested a player account creation for a v4 UUID: " + player); - } - ess.getUserMap().load(player); - return true; - } catch (UserDoesNotExistException e) { - e.printStackTrace(); - return false; + if (ess.getSettings().isDebug()) { + LOGGER.info("Vault requested a player account creation for a v4 UUID: " + player); + } + + final Player userPlayer; + if (player instanceof Player) { + userPlayer = (Player) player; + } else { + final com.earth2me.essentials.OfflinePlayer essPlayer = new com.earth2me.essentials.OfflinePlayer(player.getUniqueId(), ess.getServer()); + essPlayer.setName(player.getName()); + userPlayer = essPlayer; } + ess.getUsers().getUser(userPlayer); + return true; } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java index 3bc099e0812..f53d62940f3 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java @@ -253,7 +253,7 @@ private String replaceLine(String line, final String fullMatch, final String[] m replacer = Integer.toString(ess.getOnlinePlayers().size() - playerHidden); break; case UNIQUE: - replacer = NumberFormat.getInstance().format(ess.getUserMap().getUniqueUsers()); + replacer = NumberFormat.getInstance().format(ess.getUsers().getUserCount()); break; case WORLDS: final StringBuilder worldsBuilder = new StringBuilder(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 4eec5f85482..4cefb501da6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -11,6 +11,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -93,6 +94,31 @@ public void updateCache(final UUID uuid, final String name) { } } + public void removeCache(final UUID uuid) { + if (uuid == null) { + return; + } + + if (uuidCache.remove(uuid)) { + pendingUuidWrite.set(true); + } + + final Set toRemove = new HashSet<>(); + for (final Map.Entry entry : nameToUuidMap.entrySet()) { + if (uuid.equals(entry.getValue())) { + toRemove.add(entry.getKey()); + } + } + + for (final String name : toRemove) { + nameToUuidMap.remove(name); + } + + if (!toRemove.isEmpty()) { + pendingNameWrite.set(true); + } + } + private void loadCache() { try { if (!nameToUuidFile.exists()) { @@ -178,7 +204,7 @@ public static void writeUuidCache(final File file, Set uuids) throws IOExc } } - public static void writeNameUuidMap(File file, Map nameToUuidMap) throws IOException { + public static void writeNameUuidMap(final File file, final Map nameToUuidMap) throws IOException { try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) { for (final Map.Entry entry : nameToUuidMap.entrySet()) { dos.writeUTF(entry.getKey()); diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index b827377dd5f..91b0e693b7c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -60,6 +60,8 @@ public User getUser(final Player base) { if (user == null) { ess.getLogger().log(Level.INFO, "Essentials created a User for " + base.getName() + " (" + base.getUniqueId() + ") for non Bukkit type: " + base.getClass().getName()); user = new User(base, ess); + uuidCache.updateCache(user.getUUID(), user.getName()); + userCache.put(user.getUUID(), user); } else if (!base.equals(user.getBase())) { ess.getLogger().log(Level.INFO, "Essentials updated the underlying Player object for " + user.getUUID()); user.update(base); @@ -122,6 +124,11 @@ public User loadUncachedUser(final UUID uuid) { return null; } + public void invalidate(final UUID uuid) { + userCache.invalidate(uuid); + uuidCache.removeCache(uuid); + } + private File getUserFile(final UUID uuid) { return new File(new File(ess.getDataFolder(), "userdata"), uuid.toString() + ".yml"); } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java index 338f9e25bf9..534facd9133 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java @@ -137,7 +137,7 @@ public void sendJoinQuitMessage(final Player player, final String message, boole MessageUtil.sanitizeDiscordMarkdown(player.getDisplayName()), MessageUtil.sanitizeDiscordMarkdown(message), jda.getPlugin().getEss().getOnlinePlayers().size() - (join ? 0 : 1), - jda.getPlugin().getEss().getUserMap().getUniqueUsers()), + jda.getPlugin().getEss().getUsers().getUserCount()), player); } From f2b6f4c1f5290913d4850e9ab876dab7642de04e Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 15:21:03 -0400 Subject: [PATCH 09/38] Backport some select methods to the old UserMap class --- .../com/earth2me/essentials/Essentials.java | 1 + .../java/com/earth2me/essentials/UUIDMap.java | 135 +-------- .../java/com/earth2me/essentials/UserMap.java | 267 ++---------------- .../userstorage/ModernUUIDCache.java | 4 + .../essentials/userstorage/ModernUserMap.java | 5 + 5 files changed, 40 insertions(+), 372 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index c187fdfce36..b924c177003 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -146,6 +146,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient CustomItemResolver customItemResolver; private transient PermissionsHandler permissionsHandler; private transient AlternativeCommandsHandler alternativeCommandsHandler; + @Deprecated private transient UserMap legacyUserMap; private transient ModernUserMap userMap; private transient BalanceTopImpl balanceTop; diff --git a/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java b/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java index 21d5a228ec0..df284cbf4b7 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UUIDMap.java @@ -1,144 +1,19 @@ package com.earth2me.essentials; -import com.google.common.io.Files; -import org.bukkit.Bukkit; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.regex.Pattern; - +@Deprecated public class UUIDMap { - private static final ScheduledExecutorService writeScheduler = Executors.newScheduledThreadPool(1); - private static boolean pendingWrite; - private static boolean loading = false; - private final transient net.ess3.api.IEssentials ess; - private final File userList; - private final transient Pattern splitPattern = Pattern.compile(","); - private final Runnable writeTaskRunnable; - - public UUIDMap(final net.ess3.api.IEssentials ess) { - this.ess = ess; - userList = new File(ess.getDataFolder(), "usermap.csv"); - pendingWrite = false; - writeTaskRunnable = () -> { - if (pendingWrite) { - try { - new WriteRunner(ess.getDataFolder(), userList, ess.getUserMap().getNames()).run(); - } catch (final Throwable t) { // bad code to prevent task from being suppressed - t.printStackTrace(); - } - } - }; - writeScheduler.scheduleWithFixedDelay(writeTaskRunnable, 5, 5, TimeUnit.SECONDS); - } - - public void loadAllUsers(final ConcurrentSkipListMap names) { - try { - if (!userList.exists()) { - userList.createNewFile(); - } - - if (ess.getSettings().isDebug()) { - ess.getLogger().log(Level.INFO, "Reading usermap from disk"); - } - - if (loading) { - return; - } - - names.clear(); - loading = true; - - try (final BufferedReader reader = new BufferedReader(new FileReader(userList))) { - while (true) { - final String line = reader.readLine(); - if (line == null) { - break; - } else { - final String[] values = splitPattern.split(line); - if (values.length == 2) { - final String name = values[0]; - final UUID uuid = UUID.fromString(values[1]); - names.put(name, uuid); - } - } - } - } - loading = false; - } catch (final IOException ex) { - Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex); - } + public UUIDMap() { } public void writeUUIDMap() { - pendingWrite = true; + //no-op } public void forceWriteUUIDMap() { - if (ess.getSettings().isDebug()) { - ess.getLogger().log(Level.INFO, "Forcing usermap write to disk"); - } - pendingWrite = true; - writeTaskRunnable.run(); + //no-op } public void shutdown() { - writeScheduler.submit(writeTaskRunnable); - writeScheduler.shutdown(); - } - - private static final class WriteRunner implements Runnable { - private final File location; - private final File endFile; - private final Map names; - - private WriteRunner(final File location, final File endFile, final Map names) { - this.location = location; - this.endFile = endFile; - this.names = new HashMap<>(names); - } - - @Override - public void run() { - pendingWrite = false; - if (loading || names.isEmpty()) { - return; - } - File configFile = null; - - try { - configFile = File.createTempFile("usermap", ".tmp.csv", location); - - final BufferedWriter bWriter = new BufferedWriter(new FileWriter(configFile)); - for (final Map.Entry entry : names.entrySet()) { - bWriter.write(entry.getKey() + "," + entry.getValue().toString()); - bWriter.newLine(); - } - - bWriter.close(); - Files.move(configFile, endFile); - } catch (final IOException ex) { - try { - if (configFile != null && configFile.exists()) { - Files.move(configFile, new File(endFile.getParentFile(), "usermap.bak.csv")); - } - } catch (final Exception ex2) { - Bukkit.getLogger().log(Level.SEVERE, ex2.getMessage(), ex2); - } - Bukkit.getLogger().log(Level.WARNING, ex.getMessage(), ex); - } - } + //no-op } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserMap.java b/Essentials/src/main/java/com/earth2me/essentials/UserMap.java index 06db871a7d4..c65d91a397f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserMap.java @@ -1,287 +1,70 @@ package com.earth2me.essentials; import com.earth2me.essentials.api.UserDoesNotExistException; -import com.earth2me.essentials.utils.StringUtil; -import com.google.common.base.Charsets; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.util.concurrent.UncheckedExecutionException; -import net.ess3.api.IEssentials; -import net.ess3.api.MaxMoneyException; +import com.earth2me.essentials.userstorage.ModernUserMap; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import java.io.File; -import java.text.MessageFormat; -import java.util.Collections; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.regex.Pattern; -public class UserMap extends CacheLoader implements IConf { - private final transient IEssentials ess; - private final transient ConcurrentSkipListSet keys = new ConcurrentSkipListSet<>(); - private final transient ConcurrentSkipListMap names = new ConcurrentSkipListMap<>(); - private final UUIDMap uuidMap; - private final transient Cache users; - private final Pattern validUserPattern = Pattern.compile("^[a-zA-Z0-9_]{2,16}$"); +@Deprecated +public class UserMap { + private final transient ModernUserMap userMap; + private final transient UUIDMap uuidMap; - private static final String WARN_UUID_NOT_REPLACE = "Found UUID {0} for player {1}, but player already has a UUID ({2}). Not replacing UUID in usermap."; - - public UserMap(final IEssentials ess) { - super(); - this.ess = ess; - uuidMap = new UUIDMap(ess); - final CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); - final int maxCount = ess.getSettings().getMaxUserCacheCount(); - cacheBuilder.maximumSize(maxCount); - cacheBuilder.softValues(); - users = cacheBuilder.build(this); - } - - private void loadAllUsersAsync(final IEssentials ess) { - ess.runTaskAsynchronously(() -> { - synchronized (users) { - final File userDir = new File(ess.getDataFolder(), "userdata"); - if (!userDir.exists()) { - return; - } - keys.clear(); - users.invalidateAll(); - for (final String string : userDir.list()) { - if (!string.endsWith(".yml")) { - continue; - } - final String name = string.substring(0, string.length() - 4); - try { - keys.add(UUID.fromString(name)); - } catch (final IllegalArgumentException ex) { - //Ignore these users till they rejoin. - } - } - uuidMap.loadAllUsers(names); - } - }); + public UserMap(final ModernUserMap userMap) { + this.userMap = userMap; + this.uuidMap = new UUIDMap(); } public User getUser(final String name) { - final String sanitizedName = StringUtil.safeString(name); - try { - if (ess.getSettings().isDebug()) { - ess.getLogger().warning("Looking up username " + name + " (" + sanitizedName + ") ..."); - } - - if (names.containsKey(sanitizedName)) { - final UUID uuid = names.get(sanitizedName); - return getUser(uuid); - } - - if (ess.getSettings().isDebug()) { - ess.getLogger().warning(name + "(" + sanitizedName + ") has no known usermap entry"); - } - - final File userFile = getUserFileFromString(sanitizedName); - if (userFile.exists()) { - ess.getLogger().info("Importing user " + name + " to usermap."); - final User user = new User(new OfflinePlayer(sanitizedName, ess.getServer()), ess); - trackUUID(user.getBase().getUniqueId(), user.getName(), true); - return user; - } - return null; - } catch (final UncheckedExecutionException ex) { - if (ess.getSettings().isDebug()) { - ess.getLogger().log(Level.WARNING, ex, () -> String.format("Exception while getting user for %s (%s)", name, sanitizedName)); - } - return null; - } + return userMap.getUser(name); } public User getUser(final UUID uuid) { - try { - return ((LoadingCache) users).get(uuid.toString()); - } catch (final ExecutionException | UncheckedExecutionException ex) { - if (ess.getSettings().isDebug()) { - ess.getLogger().log(Level.WARNING, ex, () -> "Exception while getting user for " + uuid); - } - return null; - } + return userMap.getUser(uuid); } public void trackUUID(final UUID uuid, final String name, final boolean replace) { - if (uuid != null) { - keys.add(uuid); - if (name != null && name.length() > 0) { - final String keyName = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name; - if (!names.containsKey(keyName)) { - names.put(keyName, uuid); - uuidMap.writeUUIDMap(); - } else if (!isUUIDMatch(uuid, keyName)) { - if (replace) { - ess.getLogger().info("Found new UUID for " + name + ". Replacing " + names.get(keyName).toString() + " with " + uuid.toString()); - names.put(keyName, uuid); - uuidMap.writeUUIDMap(); - } else { - ess.getLogger().log(Level.INFO, MessageFormat.format(WARN_UUID_NOT_REPLACE, uuid.toString(), name, names.get(keyName).toString()), new RuntimeException()); - } - } - } - } + // no-op } - public boolean isUUIDMatch(final UUID uuid, final String name) { - return names.containsKey(name) && names.get(name).equals(uuid); - } - - @Override public User load(final String stringUUID) throws Exception { - final UUID uuid = UUID.fromString(stringUUID); - Player player = ess.getServer().getPlayer(uuid); - if (player != null) { - final User user = new User(player, ess); - trackUUID(uuid, user.getName(), true); - return user; - } - - final File userFile = getUserFileFromID(uuid); - - if (userFile.exists()) { - player = new OfflinePlayer(uuid, ess.getServer()); - final User user = new User(player, ess); - ((OfflinePlayer) player).setName(user.getLastAccountName()); - trackUUID(uuid, user.getName(), false); - return user; - } - - throw new Exception("User not found!"); + return userMap.load(UUID.fromString(stringUUID)); } public User load(final org.bukkit.OfflinePlayer player) throws UserDoesNotExistException { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null!"); - } - + final Player userPlayer; if (player instanceof Player) { - if (ess.getSettings().isDebug()) { - ess.getLogger().info("Loading online OfflinePlayer into user map..."); - } - final User user = new User((Player) player, ess); - trackUUID(player.getUniqueId(), player.getName(), true); - return user; - } - - final File userFile = getUserFileFromID(player.getUniqueId()); - if (ess.getSettings().isDebug()) { - ess.getLogger().info("Loading OfflinePlayer into user map. Has data: " + userFile.exists() + " for " + player); - } - - final OfflinePlayer essPlayer = new OfflinePlayer(player.getUniqueId(), ess.getServer()); - final User user = new User(essPlayer, ess); - if (userFile.exists()) { - essPlayer.setName(user.getLastAccountName()); + userPlayer = (Player) player; } else { - if (ess.getSettings().isDebug()) { - ess.getLogger().info("OfflinePlayer usermap load saving user data for " + player); - } - - // this code makes me sad - user.startTransaction(); - try { - user.setMoney(ess.getSettings().getStartingBalance()); - } catch (MaxMoneyException e) { - // Shouldn't happen as it would be an illegal configuration state - throw new RuntimeException(e); - } - user.setLastAccountName(user.getName()); - user.stopTransaction(); + final com.earth2me.essentials.OfflinePlayer essPlayer = new com.earth2me.essentials.OfflinePlayer(player.getUniqueId(), Bukkit.getServer()); + essPlayer.setName(player.getName()); + userPlayer = essPlayer; } - trackUUID(player.getUniqueId(), user.getName(), false); - return user; - } - - @Override - public void reloadConfig() { - getUUIDMap().forceWriteUUIDMap(); - loadAllUsersAsync(ess); - } - - public void invalidateAll() { - users.invalidateAll(); - } - - public void removeUser(final String name) { - if (names == null) { - ess.getLogger().warning("Name collection is null, cannot remove user."); - return; - } - final UUID uuid = names.get(name); - if (uuid != null) { - keys.remove(uuid); - users.invalidate(uuid.toString()); + final User user = userMap.getUser(userPlayer); + if (user == null) { + throw new UserDoesNotExistException("User not found"); } - names.remove(name); - names.remove(StringUtil.safeString(name)); - } - - public void removeUserUUID(final String uuid) { - users.invalidate(uuid); + return user; } public Set getAllUniqueUsers() { - return Collections.unmodifiableSet(keys); + return userMap.getAllUserUUIDs(); } public int getUniqueUsers() { - return keys.size(); + return userMap.getUserCount(); } protected ConcurrentSkipListMap getNames() { - return names; + return new ConcurrentSkipListMap<>(userMap.getNameCache()); } public UUIDMap getUUIDMap() { return uuidMap; } - - private File getUserFileFromID(final UUID uuid) { - final File userFolder = new File(ess.getDataFolder(), "userdata"); - return new File(userFolder, uuid.toString() + ".yml"); - } - - public File getUserFileFromString(final String name) { - final File userFolder = new File(ess.getDataFolder(), "userdata"); - return new File(userFolder, StringUtil.sanitizeFileName(name) + ".yml"); - } - - @SuppressWarnings("deprecation") - public User getUserFromBukkit(String name) { - name = StringUtil.safeString(name); - if (ess.getSettings().isDebug()) { - ess.getLogger().warning("Using potentially blocking Bukkit UUID lookup for: " + name); - } - // Don't attempt to look up entirely invalid usernames - if (name == null || !validUserPattern.matcher(name).matches()) { - return null; - } - final org.bukkit.OfflinePlayer offlinePlayer = ess.getServer().getOfflinePlayer(name); - - final UUID uuid; - try { - uuid = offlinePlayer.getUniqueId(); - } catch (final UnsupportedOperationException | NullPointerException e) { - return null; - } - // This is how Bukkit generates fake UUIDs - if (UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)).equals(uuid)) { - return null; - } else { - names.put(name, uuid); - return getUser(uuid); - } - } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 4cefb501da6..ac03b93732f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -74,6 +74,10 @@ public Set getCachedUUIDs() { return Collections.unmodifiableSet(uuidCache); } + public Map getNameCache() { + return Collections.unmodifiableMap(nameToUuidMap); + } + public int getCacheSize() { return uuidCache.size(); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index 91b0e693b7c..2a2e04e4e9d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -9,6 +9,7 @@ import org.bukkit.entity.Player; import java.io.File; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -124,6 +125,10 @@ public User loadUncachedUser(final UUID uuid) { return null; } + public Map getNameCache() { + return uuidCache.getNameCache(); + } + public void invalidate(final UUID uuid) { userCache.invalidate(uuid); uuidCache.removeCache(uuid); From 11315746283bc0034ca4d74fee862719b3412219 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 16:23:13 -0400 Subject: [PATCH 10/38] Forcefully save NPC names into uuid cache --- .../java/com/earth2me/essentials/api/Economy.java | 1 + .../economy/vault/VaultEconomyProvider.java | 1 + .../essentials/userstorage/ModernUUIDCache.java | 5 +++++ .../essentials/userstorage/ModernUserMap.java | 12 ++++++++++++ 4 files changed, 19 insertions(+) diff --git a/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java b/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java index 5ef09f8d700..568c5e68bdc 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java +++ b/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java @@ -64,6 +64,7 @@ private static void createNPCFile(String name) { npcConfig.setProperty("money", ess.getSettings().getStartingBalance()); npcConfig.blockingSave(); // This will load the NPC into the UserMap + UUID cache + ess.getUsers().addCachedNpcName(npcUUID, name); ess.getUsers().getUser(npcUUID); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java b/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java index b7eb9a9099d..0cd1da79e58 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java +++ b/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java @@ -312,6 +312,7 @@ public boolean createPlayerAccount(OfflinePlayer player) { npcConfig.setProperty("money", ess.getSettings().getStartingBalance()); npcConfig.blockingSave(); // This will load the NPC into the UserMap + UUID cache + ess.getUsers().addCachedNpcName(player.getUniqueId(), player.getName()); ess.getUsers().getUser(player.getUniqueId()); return true; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index ac03b93732f..4f85325673c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -199,6 +199,11 @@ private void saveNameToUuidCache() { } } + public void blockingSave() { + saveUuidCache(); + saveNameToUuidCache(); + } + public static void writeUuidCache(final File file, Set uuids) throws IOException { try (final DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) { for (final UUID uuid: uuids) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index 2a2e04e4e9d..7b990ded3cd 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -86,6 +86,14 @@ public User getUser(final String name) { return user; } + public void addCachedNpcName(final UUID uuid, final String name) { + if (uuid == null || name == null) { + return; + } + + uuidCache.updateCache(uuid, name); + } + @Override public User load(final UUID uuid) throws Exception { final User user = loadUncachedUser(uuid); @@ -129,6 +137,10 @@ public Map getNameCache() { return uuidCache.getNameCache(); } + public void blockingSave() { + uuidCache.blockingSave(); + } + public void invalidate(final UUID uuid) { userCache.invalidate(uuid); uuidCache.removeCache(uuid); From 614a0e64bd405c9bc6c18bddd4e96bf56ba51da0 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 16:53:35 -0400 Subject: [PATCH 11/38] Properly save the UUID/Name cache on shutdown --- .../essentials/userstorage/ModernUUIDCache.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 4f85325673c..675869cfc68 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -225,17 +225,8 @@ public static void writeNameUuidMap(final File file, final Map nam } public void shutdown() { - writeExecutor.submit(() -> { - saveNameToUuidCache(); - saveUuidCache(); - }); - try { - if (!writeExecutor.awaitTermination(30, TimeUnit.SECONDS)) { - ess.getLogger().log(Level.SEVERE, "UUID cache took too long to save!"); - } - } catch (InterruptedException e) { - ess.getLogger().log(Level.SEVERE, "Error while shutting down UUID cache", e); - } - writeExecutor.shutdown(); + writeExecutor.shutdownNow(); + saveUuidCache(); + saveNameToUuidCache(); } } From da79e8c6bb9c67010166648eca950a13f428bb58 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 17:04:17 -0400 Subject: [PATCH 12/38] Actually read more than one user/uuid --- .../earth2me/essentials/userstorage/ModernUUIDCache.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 675869cfc68..2f78070341e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -139,7 +139,9 @@ private void loadCache() { nameToUuidMap.clear(); try (final DataInputStream dis = new DataInputStream(new FileInputStream(nameToUuidFile))) { - nameToUuidMap.put(dis.readUTF(), new UUID(dis.readLong(), dis.readLong())); + while (dis.available() > 0) { + nameToUuidMap.put(dis.readUTF(), new UUID(dis.readLong(), dis.readLong())); + } } } catch (IOException e) { ess.getLogger().log(Level.SEVERE, "Error while loading Name->UUID cache", e); @@ -160,7 +162,9 @@ private void loadCache() { uuidCache.clear(); try (final DataInputStream dis = new DataInputStream(new FileInputStream(uuidCacheFile))) { - uuidCache.add(new UUID(dis.readLong(), dis.readLong())); + while (dis.available() > 0) { + uuidCache.add(new UUID(dis.readLong(), dis.readLong())); + } } } catch (IOException e) { ess.getLogger().log(Level.SEVERE, "Error while loading UUID cache", e); From 65c2d08e39394702dc6493fdebbf5c38f95d7e42 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 18:47:36 -0400 Subject: [PATCH 13/38] Add /ess usermap command for information on the usermap --- .../essentials/commands/Commandessentials.java | 17 +++++++++++++++++ .../essentials/userstorage/ModernUserMap.java | 4 ++++ .../src/main/resources/messages.properties | 2 ++ 3 files changed, 23 insertions(+) diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 92dcf4c8a7c..38e45ce8216 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -132,6 +132,9 @@ public void run(final Server server, final CommandSource sender, final String co case "homes": runHomes(server, sender, commandLabel, args); break; + case "usermap": + runUserMap(sender, args); + break; // "#EasterEgg" case "nya": @@ -500,6 +503,20 @@ private void runHomes(final Server server, final CommandSource sender, final Str } } + // Gets information about cached users + private void runUserMap(final CommandSource sender, final String[] args) { + if (!sender.isAuthorized("essentials.usermap", ess)) { + return; + } + + sender.sendMessage(tl("usermapSize", ess.getUsers().getCachedCount(), ess.getUsers().getUserCount(), ess.getSettings().getMaxUserCacheCount())); + if (args.length > 1 && args[1].equals("full")) { + for (final Map.Entry entry : ess.getUsers().getNameCache().entrySet()) { + sender.sendMessage(tl("usermapEntry", entry.getKey(), entry.getValue().toString())); + } + } + } + // Displays versions of EssentialsX and related plugins. private void runVersion(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { if (sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.version")) return; diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index 7b990ded3cd..d91c2f7ac37 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -33,6 +33,10 @@ public Set getAllUserUUIDs() { return uuidCache.getCachedUUIDs(); } + public long getCachedCount() { + return userCache.size(); + } + public int getUserCount() { return uuidCache.getCacheSize(); } diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index a140e3036cb..34cea98802c 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1378,6 +1378,8 @@ userIsAwaySelf=\u00a77You are now AFK. userIsAwaySelfWithMessage=\u00a77You are now AFK. userIsNotAwaySelf=\u00a77You are no longer AFK. userJailed=\u00a76You have been jailed\! +usermapSize=\u00a76Current cached users in user map is \u00a7c{0}\u00a76/\u00a7c{1}\u00a76/\u00a7c{2}\u00a76. +usermapEntry=\u00a7c{0} \u00a76is mapped to \u00a7c{1}\u00a76. userUnknown=\u00a74Warning\: The user ''\u00a7c{0}\u00a74'' has never joined this server. usingTempFolderForTesting=Using temp folder for testing\: vanish=\u00a76Vanish for {0}\u00a76\: {1} From 6e67a0a0644e9a27a19ac2faa205f5d0a84e4858 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 18:48:50 -0400 Subject: [PATCH 14/38] Add usermap.bin and uuids.bin to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 36866d90769..44ea237797a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ /Essentials/kits.yml /Essentials/userdata/testplayer1.yml /Essentials/usermap.csv +/Essentials/usermap.bin +/Essentials/uuids.bin # Build files .gradle/ From 79318a115670647b16faeddb4012acd3a2411aa6 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 20:36:21 -0400 Subject: [PATCH 15/38] Make StringUtil#safeString null-safe --- .../main/java/com/earth2me/essentials/utils/StringUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/StringUtil.java b/Essentials/src/main/java/com/earth2me/essentials/utils/StringUtil.java index 1598de680de..7f92ff99e1a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/StringUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/StringUtil.java @@ -21,6 +21,9 @@ public static String sanitizeFileName(final String name) { //Used to clean strings/names before saving as filenames/permissions public static String safeString(final String string) { + if (string == null) { + return null; + } return STRICTINVALIDCHARS.matcher(string.toLowerCase(Locale.ENGLISH)).replaceAll("_"); } From 289fee470a3576dcec9a638db13503749cb001e8 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 17 Oct 2021 22:48:05 -0400 Subject: [PATCH 16/38] Move config save hook to contructor --- .../main/java/com/earth2me/essentials/UserData.java | 11 +++-------- .../essentials/config/EssentialsConfiguration.java | 13 +++++++++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserData.java b/Essentials/src/main/java/com/earth2me/essentials/UserData.java index d9cf1bf0864..2e5bb76bf67 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserData.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserData.java @@ -48,6 +48,9 @@ protected UserData(final Player base, final IEssentials ess) { } config = new EssentialsUserConfiguration(base.getName(), base.getUniqueId(), new File(folder, base.getUniqueId() + ".yml")); + config.setSaveHook(() -> { + config.setRootHolder(UserConfigHolder.class, holder); + }); reloadConfig(); if (config.getUsername() == null) { @@ -82,14 +85,6 @@ public final void reloadConfig() { ess.getLogger().log(Level.SEVERE, "Error while reading user config: " + e.getMessage(), e); throw new RuntimeException(e); } - config.setSaveHook(() -> { - try { - config.getRootNode().set(UserConfigHolder.class, holder); - } catch (SerializationException e) { - ess.getLogger().log(Level.SEVERE, "Error while saving user config: " + e.getMessage(), e); - throw new RuntimeException(e); - } - }); money = _getMoney(); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java index 36c9ae70cd1..09132917cc3 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java @@ -104,6 +104,15 @@ public CommentedConfigurationNode getRootNode() { return configurationNode; } + public void setRootHolder(final Class holderClass, final Object holder) { + try { + getRootNode().set(holderClass, holder); + } catch (SerializationException e) { + LOGGER.log(Level.SEVERE, "Error while saving user config: " + e.getMessage(), e); + throw new RuntimeException(e); + } + } + public File getFile() { return configFile; } @@ -367,10 +376,10 @@ public synchronized void load() { } catch (final ParsingException e) { final File broken = new File(configFile.getAbsolutePath() + ".broken." + System.currentTimeMillis()); if (configFile.renameTo(broken)) { - LOGGER.log(Level.SEVERE, "The file " + configFile.toString() + " is broken, it has been renamed to " + broken.toString(), e.getCause()); + LOGGER.log(Level.SEVERE, "The file " + configFile + " is broken, it has been renamed to " + broken, e.getCause()); return; } - LOGGER.log(Level.SEVERE, "The file " + configFile.toString() + " is broken. A backup file has failed to be created", e.getCause()); + LOGGER.log(Level.SEVERE, "The file " + configFile + " is broken. A backup file has failed to be created", e.getCause()); } catch (final ConfigurateException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } finally { From b6097e7eb5a9c59b3fb2a4a28cba5f1d78a98b08 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 18 Oct 2021 15:16:25 -0400 Subject: [PATCH 17/38] Initialize legacy user map --- Essentials/src/main/java/com/earth2me/essentials/Essentials.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index b924c177003..ef77d5a7b89 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -291,6 +291,7 @@ public void onEnable() { execTimer.mark("Init(Mail)"); userMap = new ModernUserMap(this); + legacyUserMap = new UserMap(userMap); execTimer.mark("Init(Usermap)"); balanceTop = new BalanceTopImpl(this); From 904e664240e1fefac61d1d9b83b4a8b3cf1d34f0 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 4 Nov 2021 11:43:26 -0400 Subject: [PATCH 18/38] Update Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java --- .../com/earth2me/essentials/userstorage/ModernUUIDCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 2f78070341e..f580206069b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -33,7 +33,7 @@ public class ModernUUIDCache { * * mdcfe doesn't log in the server for 31 days * * JRoy changes his name to mdcfeYT420 * * mdcfeYT420 (previously JRoy) logs in the server - * * In a UUID->Name based map, multiple uuids now point to the same map + * In a UUID->Name based map, multiple uuids now point to the same map * preventing any command which allows for offline players to resolve a * single uuid from a name. * From 1f6d8238b20c4ffa823b02979336562dfd142349 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Wed, 15 Dec 2021 20:09:13 -0500 Subject: [PATCH 19/38] Update Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java --- .../com/earth2me/essentials/userstorage/ModernUUIDCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index f580206069b..2a66573e0ad 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -37,7 +37,7 @@ public class ModernUUIDCache { * preventing any command which allows for offline players to resolve a * single uuid from a name. * - * This map is baked by a file-based cache. If this cache is missing, a new + * This map is backed by a file-based cache. If this cache is missing, a new * one is populated by iterating over all files in the userdata folder and * caching the {@code last-account-name} value. */ From c1b71474e886b5f334f29a885452502480a980b7 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 22 May 2022 20:00:39 -0400 Subject: [PATCH 20/38] Fix compile error --- .../com/earth2me/essentials/perm/impl/LuckPermsHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java b/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java index 77fff460fc5..553f9eeeb45 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java +++ b/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java @@ -70,7 +70,7 @@ public void calculate(final Player target, final ContextConsumer consumer) { // If the player doesn't exist in the UserMap, just skip // Ess will cause performance problems for permissions checks if it attempts to // perform i/o to load the user data otherwise. - if (!ess.getUserMap().userExists(target.getUniqueId())) { + if (!ess.getUsers().getAllUserUUIDs().contains(target.getUniqueId())) { return; } From 035c87bd8a9a85b3001414108ac014ff0b03510c Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 25 Jun 2022 16:32:51 -0400 Subject: [PATCH 21/38] Add a few more debug commands --- .../commands/Commandessentials.java | 24 +++++++++++++++---- .../essentials/userstorage/ModernUserMap.java | 4 ++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index dc4dd188ced..9df3b3d397c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -4,6 +4,7 @@ import com.earth2me.essentials.User; import com.earth2me.essentials.economy.EconomyLayer; import com.earth2me.essentials.economy.EconomyLayers; +import com.earth2me.essentials.userstorage.ModernUserMap; import com.earth2me.essentials.utils.DateUtil; import com.earth2me.essentials.utils.EnumUtil; import com.earth2me.essentials.utils.FloatUtil; @@ -546,10 +547,25 @@ private void runUserMap(final CommandSource sender, final String[] args) { return; } - sender.sendMessage(tl("usermapSize", ess.getUsers().getCachedCount(), ess.getUsers().getUserCount(), ess.getSettings().getMaxUserCacheCount())); - if (args.length > 1 && args[1].equals("full")) { - for (final Map.Entry entry : ess.getUsers().getNameCache().entrySet()) { - sender.sendMessage(tl("usermapEntry", entry.getKey(), entry.getValue().toString())); + final ModernUserMap userMap = ess.getUsers(); + sender.sendMessage(tl("usermapSize", userMap.getCachedCount(), userMap.getUserCount(), ess.getSettings().getMaxUserCacheCount())); + if (args.length > 1) { + if (args[1].equals("full")) { + for (final Map.Entry entry : userMap.getNameCache().entrySet()) { + sender.sendMessage(tl("usermapEntry", entry.getKey(), entry.getValue().toString())); + } + } else { + try { + final UUID uuid = UUID.fromString(args[1]); + for (final Map.Entry entry : userMap.getNameCache().entrySet()) { + if (entry.getValue().equals(uuid)) { + sender.sendMessage(tl("usermapEntry", entry.getKey(), args[1])); + } + } + } catch (IllegalArgumentException ignored) { + final String sanitizedName = userMap.getSanitizedName(args[1]); + sender.sendMessage(tl("usermapEntry", sanitizedName, userMap.getNameCache().get(sanitizedName).toString())); + } } } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index d91c2f7ac37..d3a94e42073 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -141,6 +141,10 @@ public Map getNameCache() { return uuidCache.getNameCache(); } + public String getSanitizedName(final String name) { + return uuidCache.getSanitizedName(name); + } + public void blockingSave() { uuidCache.blockingSave(); } From 0627bdaa603333fe26b143543e732a67b6069e32 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 25 Jun 2022 17:20:37 -0400 Subject: [PATCH 22/38] Fix usermap not getting saved during new uuid insertions --- .../com/earth2me/essentials/userstorage/ModernUUIDCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 2a66573e0ad..c88c5f7956e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -92,7 +92,7 @@ public void updateCache(final UUID uuid, final String name) { } if (name != null) { final UUID replacedUuid = nameToUuidMap.put(getSanitizedName(name), uuid); - if (uuid.equals(replacedUuid)) { + if (!uuid.equals(replacedUuid)) { pendingNameWrite.set(true); } } From 2fc006f78aef1cfebffc90b75909f3eb8df821aa Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 25 Jun 2022 17:30:11 -0400 Subject: [PATCH 23/38] Add some additional debug messages --- .../userstorage/ModernUUIDCache.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index c88c5f7956e..753e79519c5 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -91,8 +91,12 @@ public void updateCache(final UUID uuid, final String name) { pendingUuidWrite.set(true); } if (name != null) { - final UUID replacedUuid = nameToUuidMap.put(getSanitizedName(name), uuid); + final String sanitizedName = getSanitizedName(name); + final UUID replacedUuid = nameToUuidMap.put(sanitizedName, uuid); if (!uuid.equals(replacedUuid)) { + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.WARNING, "Replaced UUID during cache update for " + sanitizedName + ": " + replacedUuid + " -> " + uuid); + } pendingNameWrite.set(true); } } @@ -124,6 +128,8 @@ public void removeCache(final UUID uuid) { } private void loadCache() { + final boolean debug = ess.getSettings().isDebug(); + try { if (!nameToUuidFile.exists()) { if (!nameToUuidFile.createNewFile()) { @@ -132,7 +138,7 @@ private void loadCache() { return; } - if (ess.getSettings().isDebug()) { + if (debug) { ess.getLogger().log(Level.INFO, "Loading Name->UUID cache from disk..."); } @@ -140,7 +146,12 @@ private void loadCache() { try (final DataInputStream dis = new DataInputStream(new FileInputStream(nameToUuidFile))) { while (dis.available() > 0) { - nameToUuidMap.put(dis.readUTF(), new UUID(dis.readLong(), dis.readLong())); + final String username = dis.readUTF(); + final UUID uuid = new UUID(dis.readLong(), dis.readLong()); + final UUID previous = nameToUuidMap.put(username, uuid); + if (previous != null && debug) { + ess.getLogger().log(Level.WARNING, "Replaced UUID during cache load for " + username + ": " + previous + " -> " + uuid); + } } } } catch (IOException e) { @@ -155,7 +166,7 @@ private void loadCache() { return; } - if (ess.getSettings().isDebug()) { + if (debug) { ess.getLogger().log(Level.INFO, "Loading UUID cache from disk..."); } @@ -163,7 +174,11 @@ private void loadCache() { try (final DataInputStream dis = new DataInputStream(new FileInputStream(uuidCacheFile))) { while (dis.available() > 0) { - uuidCache.add(new UUID(dis.readLong(), dis.readLong())); + final UUID uuid = new UUID(dis.readLong(), dis.readLong()); + if (uuidCache.contains(uuid) && debug) { + ess.getLogger().log(Level.WARNING, "UUID " + uuid + " duplicated in cache"); + } + uuidCache.add(uuid); } } } catch (IOException e) { From 169bed1974921b44d96b8512f4dcb6e4dca79597 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Jun 2022 14:16:02 -0400 Subject: [PATCH 24/38] Prefer UUIDv4 during usermap initialization --- .../essentials/EssentialsUpgrade.java | 19 +++++++++++++++---- .../userstorage/ModernUUIDCache.java | 3 +-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index 8b2ac706f85..c030f7f6e97 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -917,7 +917,7 @@ private void updateBan(final String playerName, final String banReason, final Lo } public void generateUidCache() { - if (doneFile.getBoolean("newUidCacheGenerated", false)) { + if (doneFile.getBoolean("newUidCacheBuilt", false)) { return; } @@ -927,7 +927,7 @@ public void generateUidCache() { if (!userdataFolder.isDirectory() || usermapFile.exists() || uidsFile.exists()) { ess.getLogger().warning("Missing userdata folder, aborting"); - doneFile.setProperty("newUidCacheGenerated", true); + doneFile.setProperty("newUidCacheBuilt", true); doneFile.save(); return; } @@ -952,8 +952,19 @@ public void generateUidCache() { String name = config.getString("last-account-name", null); name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name; - uuids.add(uuid); if (name != null) { + if (nameToUuidMap.containsKey(name)) { + final UUID oldUuid = nameToUuidMap.get(name); + if (oldUuid.version() <= uuid.version()) { + ess.getLogger().warning("New UUID found for " + name + ": " + uuid + " (old: " + oldUuid + "). Replacing."); + uuids.remove(oldUuid); + } else { + ess.getLogger().warning("Found UUID for " + name + ": " + uuid + " (old: " + oldUuid + "). Skipping."); + continue; + } + } + + uuids.add(uuid); nameToUuidMap.put(name, uuid); } } catch (IllegalArgumentException | IndexOutOfBoundsException ignored) { @@ -969,7 +980,7 @@ public void generateUidCache() { ModernUUIDCache.writeUuidCache(uidsFile, uuids); } - doneFile.setProperty("newUidCacheGenerated", true); + doneFile.setProperty("newUidCacheBuilt", true); doneFile.save(); } catch (final IOException e) { ess.getLogger().log(Level.SEVERE, "Error while generating initial uuids/names cache", e); diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index 753e79519c5..fa4e097101d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -245,7 +245,6 @@ public static void writeNameUuidMap(final File file, final Map nam public void shutdown() { writeExecutor.shutdownNow(); - saveUuidCache(); - saveNameToUuidCache(); + blockingSave(); } } From 71595d3bcffcbe1484120658a2874a8597816e28 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:08:27 -0400 Subject: [PATCH 25/38] Add more usermap debug commands --- .../essentials/EssentialsUpgrade.java | 2 +- .../commands/Commandessentials.java | 49 +++++++++++++++++++ .../src/main/resources/messages.properties | 3 +- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index c030f7f6e97..fb3ea5c01da 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -54,7 +54,7 @@ public class EssentialsUpgrade { private final static Logger LOGGER = Logger.getLogger("Essentials"); - private static final FileFilter YML_FILTER = pathname -> pathname.isFile() && pathname.getName().endsWith(".yml"); + public static final FileFilter YML_FILTER = pathname -> pathname.isFile() && pathname.getName().endsWith(".yml"); private static final String PATTERN_CONFIG_UUID_REGEX = "(?mi)^uuid:\\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\s*$"; private static final Pattern PATTERN_CONFIG_UUID = Pattern.compile(PATTERN_CONFIG_UUID_REGEX); private static final String PATTERN_CONFIG_NAME_REGEX = "(?mi)^lastAccountName:\\s*[\"\']?(\\w+)[\"\']?\\s*$"; diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 9df3b3d397c..4277af2b150 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -1,6 +1,7 @@ package com.earth2me.essentials.commands; import com.earth2me.essentials.CommandSource; +import com.earth2me.essentials.EssentialsUpgrade; import com.earth2me.essentials.User; import com.earth2me.essentials.economy.EconomyLayer; import com.earth2me.essentials.economy.EconomyLayers; @@ -29,6 +30,7 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitRunnable; +import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.nio.charset.StandardCharsets; @@ -40,12 +42,15 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import java.util.logging.Level; import java.util.stream.Collectors; import static com.earth2me.essentials.I18n.tl; @@ -554,6 +559,50 @@ private void runUserMap(final CommandSource sender, final String[] args) { for (final Map.Entry entry : userMap.getNameCache().entrySet()) { sender.sendMessage(tl("usermapEntry", entry.getKey(), entry.getValue().toString())); } + } else if (args[1].equals("purge")) { + final boolean seppuku = args.length > 2 && args[2].equals("iknowwhatimdoing"); + + sender.sendMessage(tl("usermapPurge", String.valueOf(seppuku))); + + final Set uuids = new HashSet<>(ess.getUsers().getAllUserUUIDs()); + ess.runTaskAsynchronously(() -> { + final File userdataFolder = new File(ess.getDataFolder(), "userdata"); + final File backupFolder = new File(ess.getDataFolder(), "userdata-npc-backup-boogaloo"); + + if (!userdataFolder.isDirectory()) { + ess.getLogger().warning("Missing userdata folder, aborting usermap purge."); + return; + } + + if (seppuku && !backupFolder.mkdir()) { + ess.getLogger().warning("Unable to create backup folder, aborting usermap purge."); + return; + } + + int total = 0; + final File[] files = userdataFolder.listFiles(EssentialsUpgrade.YML_FILTER); + if (files != null) { + for (final File file : files) { + try { + final String fileName = file.getName(); + final UUID uuid = UUID.fromString(fileName.substring(0, fileName.length() - 4)); + if (!uuids.contains(uuid)) { + total++; + ess.getLogger().warning("Found orphaned userdata file: " + file.getName()); + if (seppuku) { + try { + com.google.common.io.Files.move(file, new File(backupFolder, file.getName())); + } catch (IOException e) { + ess.getLogger().log(Level.WARNING, "Unable to move orphaned userdata file: " + file.getName(), e); + } + } + } + } catch (IllegalArgumentException ignored) { + } + } + } + ess.getLogger().info("Found " + total + " orphaned userdata files."); + }); } else { try { final UUID uuid = UUID.fromString(args[1]); diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index 7b87a4d298f..f3d225dc59e 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1412,8 +1412,9 @@ userIsAwaySelf=\u00a77You are now AFK. userIsAwaySelfWithMessage=\u00a77You are now AFK. userIsNotAwaySelf=\u00a77You are no longer AFK. userJailed=\u00a76You have been jailed\! -usermapSize=\u00a76Current cached users in user map is \u00a7c{0}\u00a76/\u00a7c{1}\u00a76/\u00a7c{2}\u00a76. usermapEntry=\u00a7c{0} \u00a76is mapped to \u00a7c{1}\u00a76. +usermapPurge=\u00a76Checking for files in userdata that are not mapped, results will be logged to console. Destructive Mode: {0} +usermapSize=\u00a76Current cached users in user map is \u00a7c{0}\u00a76/\u00a7c{1}\u00a76/\u00a7c{2}\u00a76. userUnknown=\u00a74Warning\: The user ''\u00a7c{0}\u00a74'' has never joined this server. usingTempFolderForTesting=Using temp folder for testing\: vanish=\u00a76Vanish for {0}\u00a76\: {1} From 2d04796432cb88e78870d882b1eff0146fbce994 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:31:07 -0400 Subject: [PATCH 26/38] joker moment --- .../src/main/java/com/earth2me/essentials/EssentialsUpgrade.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index 98a9a22f948..f1f8178253a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -47,7 +47,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; -import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.earth2me.essentials.I18n.tl; From c7f3fbadcb7bd4a4dc7ba54fe12e9c8d8aae123e Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:36:18 -0400 Subject: [PATCH 27/38] ... --- .../com/earth2me/essentials/config/EssentialsConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java index 3200c2876c3..e53fa07a89b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java @@ -107,7 +107,7 @@ public void setRootHolder(final Class holderClass, final Object holder) { try { getRootNode().set(holderClass, holder); } catch (SerializationException e) { - LOGGER.log(Level.SEVERE, "Error while saving user config: " + configFile.getName(), e); + Essentials.getWrappedLogger().log(Level.SEVERE, "Error while saving user config: " + configFile.getName(), e); throw new RuntimeException(e); } } From 4601b785f57f437ebf02a8aacc6f0f15889abbaa Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Jun 2022 18:55:30 -0400 Subject: [PATCH 28/38] Fix a few typos in javadocs --- .../earth2me/essentials/userstorage/ModernUUIDCache.java | 6 +++--- .../com/earth2me/essentials/userstorage/ModernUserMap.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java index fa4e097101d..1011516042e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUUIDCache.java @@ -33,9 +33,9 @@ public class ModernUUIDCache { * * mdcfe doesn't log in the server for 31 days * * JRoy changes his name to mdcfeYT420 * * mdcfeYT420 (previously JRoy) logs in the server - * In a UUID->Name based map, multiple uuids now point to the same map - * preventing any command which allows for offline players to resolve a - * single uuid from a name. + * In a UUID->Name based map, different uuids now point to the same name + * preventing any command which allows for offline players from resolving a + * given uuid from a given name. * * This map is backed by a file-based cache. If this cache is missing, a new * one is populated by iterating over all files in the userdata folder and diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index d3a94e42073..10ebba557ad 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -110,7 +110,7 @@ public User load(final UUID uuid) throws Exception { /** * Gets a User by the given UUID in the cache, if present, otherwise loads the user without placing them in the cache. - * Ideally to be used when running operations on all stored. + * Ideally to be used when running operations on all stored users. */ public User loadUncachedUser(final UUID uuid) { User user = userCache.getIfPresent(uuid); From 963d92ea2dfe84d8af88a74d45317ff512ab3276 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 23 Jul 2022 19:09:14 -0400 Subject: [PATCH 29/38] Compare logout timestamps when replacing same uuid version --- .../java/com/earth2me/essentials/EssentialsUpgrade.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index f1f8178253a..ab3d46185f6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -936,7 +936,7 @@ public void generateUidCache() { return; } - final Set uuids = new HashSet<>(); + final Map uuids = new HashMap<>(); final Map nameToUuidMap = new HashMap<>(); final File[] files = userdataFolder.listFiles(YML_FILTER); @@ -949,11 +949,12 @@ public void generateUidCache() { config.load(); String name = config.getString("last-account-name", null); name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name; + final long time = config.getLong("timestamps.logout", 0L); if (name != null) { if (nameToUuidMap.containsKey(name)) { final UUID oldUuid = nameToUuidMap.get(name); - if (oldUuid.version() <= uuid.version()) { + if (oldUuid.version() < uuid.version() || (oldUuid.version() == uuid.version() && uuids.get(oldUuid) < time)) { ess.getLogger().warning("New UUID found for " + name + ": " + uuid + " (old: " + oldUuid + "). Replacing."); uuids.remove(oldUuid); } else { @@ -962,7 +963,7 @@ public void generateUidCache() { } } - uuids.add(uuid); + uuids.put(uuid, config.getLong("timestamps.logout", 0L)); nameToUuidMap.put(name, uuid); } } catch (IllegalArgumentException | IndexOutOfBoundsException ignored) { @@ -975,7 +976,7 @@ public void generateUidCache() { } if (!uuids.isEmpty()) { - ModernUUIDCache.writeUuidCache(uidsFile, uuids); + ModernUUIDCache.writeUuidCache(uidsFile, uuids.keySet()); } doneFile.setProperty("newUidCacheBuilt", true); From d7a28345bd7fcf03ae26c36edfe4bd9c39e34d41 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Tue, 26 Jul 2022 18:21:26 -0400 Subject: [PATCH 30/38] Don't cache users into map for luckperms contexts --- .../com/earth2me/essentials/perm/impl/LuckPermsHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java b/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java index 553f9eeeb45..e4db80360be 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java +++ b/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java @@ -74,7 +74,7 @@ public void calculate(final Player target, final ContextConsumer consumer) { return; } - final User user = ess.getUser(target); + final User user = ess.getUsers().loadUncachedUser(target.getUniqueId()); for (Calculator calculator : this.calculators) { calculator.function.apply(user).forEach(value -> consumer.accept(calculator.id, value)); } From 42f52bf2eb0f5921e14bdc4f0b8b586d3ffb1ea5 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 13 Aug 2022 10:56:52 -0400 Subject: [PATCH 31/38] Always update usernames on join Always call ModernUserMap#getUser(Player) during join to ensure the fetched/created User object has the most up-to-date name on the underlying Player object. Additionally, ensure that ModernUUIDCache#updateCache is always called during #getUser and not just when we generate a new User object (which always never happens by the time the call is made :skull:). --- .../java/com/earth2me/essentials/Essentials.java | 12 +----------- .../essentials/userstorage/ModernUserMap.java | 4 ++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 03722aacfbb..1daec0980a3 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -1067,17 +1067,7 @@ public User getUser(final Player base) { return null; } - User user = userMap.getUser(base.getUniqueId()); - - if (user == null) { - if (getSettings().isDebug()) { - LOGGER.log(Level.INFO, "Constructing new userfile from base player " + base.getName()); - } - user = new User(base, this); - } else { - user.update(base); - } - return user; + return userMap.getUser(base); } private void handleCrash(final Throwable exception) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index 10ebba557ad..26ec9c1d69a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -65,12 +65,12 @@ public User getUser(final Player base) { if (user == null) { ess.getLogger().log(Level.INFO, "Essentials created a User for " + base.getName() + " (" + base.getUniqueId() + ") for non Bukkit type: " + base.getClass().getName()); user = new User(base, ess); - uuidCache.updateCache(user.getUUID(), user.getName()); - userCache.put(user.getUUID(), user); } else if (!base.equals(user.getBase())) { ess.getLogger().log(Level.INFO, "Essentials updated the underlying Player object for " + user.getUUID()); user.update(base); } + uuidCache.updateCache(user.getUUID(), user.getName()); + userCache.put(user.getUUID(), user); return user; } From dcb64006eb58a808d5962579951a920e82c31323 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 13 Aug 2022 15:49:44 -0400 Subject: [PATCH 32/38] Add ModernUserMap#loadUncachedUser(Player) Used to create User objects based on player objects for use from when before Bukkit places the user into its own internal usermap. Without this, ModernUserMap#loadUncachedUser(UUID) would attempt to fetch the player from the server only to not return null because we first try to create a user during PlayerLoginEvent which is before Bukkit puts the player into its internal usermap. --- .../com/earth2me/essentials/Essentials.java | 2 +- .../essentials/userstorage/ModernUserMap.java | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 1daec0980a3..efad61fa3b9 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -1067,7 +1067,7 @@ public User getUser(final Player base) { return null; } - return userMap.getUser(base); + return userMap.loadUncachedUser(base); } private void handleCrash(final Throwable exception) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java index 26ec9c1d69a..4eb95d200bc 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java +++ b/Essentials/src/main/java/com/earth2me/essentials/userstorage/ModernUserMap.java @@ -57,19 +57,7 @@ public User getUser(final UUID uuid) { } public User getUser(final Player base) { - if (base == null) { - return null; - } - - User user = getUser(base.getUniqueId()); - if (user == null) { - ess.getLogger().log(Level.INFO, "Essentials created a User for " + base.getName() + " (" + base.getUniqueId() + ") for non Bukkit type: " + base.getClass().getName()); - user = new User(base, ess); - } else if (!base.equals(user.getBase())) { - ess.getLogger().log(Level.INFO, "Essentials updated the underlying Player object for " + user.getUUID()); - user.update(base); - } - uuidCache.updateCache(user.getUUID(), user.getName()); + final User user = loadUncachedUser(base); userCache.put(user.getUUID(), user); return user; } @@ -108,6 +96,24 @@ public User load(final UUID uuid) throws Exception { throw new Exception("User not found!"); } + public User loadUncachedUser(final Player base) { + if (base == null) { + return null; + } + + User user = getUser(base.getUniqueId()); + if (user == null) { + ess.getLogger().log(Level.INFO, "Essentials created a User for " + base.getName() + " (" + base.getUniqueId() + ") for non Bukkit type: " + base.getClass().getName()); + user = new User(base, ess); + } else if (!base.equals(user.getBase())) { + ess.getLogger().log(Level.INFO, "Essentials updated the underlying Player object for " + user.getUUID()); + user.update(base); + } + uuidCache.updateCache(user.getUUID(), user.getName()); + + return user; + } + /** * Gets a User by the given UUID in the cache, if present, otherwise loads the user without placing them in the cache. * Ideally to be used when running operations on all stored users. From eb5c42dc490333cbde9ad63a555dc98d469db194 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 14 Aug 2022 19:07:35 -0400 Subject: [PATCH 33/38] if i broke one more damn thing, i'm reverting everything --- .../java/com/earth2me/essentials/Essentials.java | 12 +++++++++++- .../essentials/EssentialsPlayerListener.java | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index efad61fa3b9..c9369255d26 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -1067,7 +1067,17 @@ public User getUser(final Player base) { return null; } - return userMap.loadUncachedUser(base); + User user = userMap.getUser(base.getUniqueId()); + + if (user == null) { + if (getSettings().isDebug()) { + LOGGER.log(Level.INFO, "Constructing new userfile from base player " + base.getName()); + } + user = userMap.loadUncachedUser(base); + } else { + user.update(base); + } + return user; } private void handleCrash(final Throwable exception) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index 5af86d1c969..8ec2521c662 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -308,6 +308,7 @@ public void delayedJoin(final Player player, final String message) { ess.getBackup().onPlayerJoin(); final User dUser = ess.getUser(player); + dUser.update(player); dUser.startTransaction(); if (dUser.isNPC()) { @@ -527,6 +528,7 @@ public void onPlayerLoginBanned(final PlayerLoginEvent event) { public void onPlayerLogin(final PlayerLoginEvent event) { if (event.getResult() == Result.KICK_FULL) { final User kfuser = ess.getUser(event.getPlayer()); + kfuser.update(event.getPlayer()); if (kfuser.isAuthorized("essentials.joinfullserver")) { event.allow(); return; From 6fcd963eacada5b05db670b615462fa9a4f58c35 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:14:22 -0400 Subject: [PATCH 34/38] Use safe string for Economy and VaultEconomyProvider always --- .../src/main/java/com/earth2me/essentials/api/Economy.java | 2 +- .../earth2me/essentials/economy/vault/VaultEconomyProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java b/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java index c0c7737b0ed..cc1625992a4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java +++ b/Essentials/src/main/java/com/earth2me/essentials/api/Economy.java @@ -96,7 +96,7 @@ private static User getUserByName(final String name) { } if (user == null) { - user = getUserByUUID(UUID.nameUUIDFromBytes(("NPC:" + (ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name)).getBytes(Charsets.UTF_8))); + user = getUserByUUID(UUID.nameUUIDFromBytes(("NPC:" + StringUtil.safeString(name)).getBytes(Charsets.UTF_8))); } return user; diff --git a/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java b/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java index 1f8a6023188..d231c56ab40 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java +++ b/Essentials/src/main/java/com/earth2me/essentials/economy/vault/VaultEconomyProvider.java @@ -80,7 +80,7 @@ public boolean hasAccount(String playerName) { return true; } // We may not have the player name in the usermap, let's double check an NPC account with this name doesn't exist. - return com.earth2me.essentials.api.Economy.playerExists(UUID.nameUUIDFromBytes(("NPC:" + (ess.getSettings().isSafeUsermap() ? StringUtil.safeString(playerName) : playerName)).getBytes(Charsets.UTF_8))); + return com.earth2me.essentials.api.Economy.playerExists(UUID.nameUUIDFromBytes(("NPC:" + StringUtil.safeString(playerName)).getBytes(Charsets.UTF_8))); } @Override From 0dc920025e0d6bc5c229d2edf57204fcdc0b573f Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:36:43 -0400 Subject: [PATCH 35/38] Change some method visibility, create IUserMap --- .idea/checkstyle-idea.xml | 4 +- .../com/earth2me/essentials/IEssentials.java | 4 +- .../com/earth2me/essentials/UserData.java | 6 ++- .../com/earth2me/essentials/api/Economy.java | 3 +- .../commands/Commandessentials.java | 2 +- .../essentials/userstorage/IUserMap.java | 51 +++++++++++++++++++ .../userstorage/ModernUUIDCache.java | 18 +++---- .../essentials/userstorage/ModernUserMap.java | 16 ++++-- 8 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 Essentials/src/main/java/com/earth2me/essentials/userstorage/IUserMap.java diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index 4b03e0aa643..7f8801ea8f5 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -7,13 +7,13 @@ true