Merge pull request #2425 from BentoBoxWorld/2424_Name_lookup_from_UUID_takes_too_long

2424 name lookup from UUID takes too long
This commit is contained in:
tastybento 2024-07-05 11:04:45 -07:00 committed by GitHub
commit 251abae6cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 197 additions and 7 deletions

View File

@ -1,5 +1,7 @@
package world.bentobox.bentobox.database.json; package world.bentobox.bentobox.database.json;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
import org.bukkit.Location; import org.bukkit.Location;
@ -25,9 +27,11 @@ import world.bentobox.bentobox.database.json.adapters.FlagTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.ItemStackTypeAdapter; import world.bentobox.bentobox.database.json.adapters.ItemStackTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter; import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.MaterialTypeAdapter; import world.bentobox.bentobox.database.json.adapters.MaterialTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.PairTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter; import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.VectorTypeAdapter; import world.bentobox.bentobox.database.json.adapters.VectorTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter; import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter;
import world.bentobox.bentobox.util.Pair;
/** /**
@ -74,6 +78,13 @@ public class BentoboxTypeAdapterFactory implements TypeAdapterFactory {
return (TypeAdapter<T>) new WorldTypeAdapter(); return (TypeAdapter<T>) new WorldTypeAdapter();
} else if (Vector.class.isAssignableFrom(rawType)) { } else if (Vector.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new VectorTypeAdapter(); return (TypeAdapter<T>) new VectorTypeAdapter();
} else if (Pair.class.isAssignableFrom(rawType)) {
// Add Pair handling here with type safety
Type pairType = type.getType();
ParameterizedType parameterizedType = (ParameterizedType) pairType;
Type xType = parameterizedType.getActualTypeArguments()[0];
Type zType = parameterizedType.getActualTypeArguments()[1];
return (TypeAdapter<T>) new PairTypeAdapter<>(xType, zType);
} else if (ConfigurationSerializable.class.isAssignableFrom(rawType)) { } else if (ConfigurationSerializable.class.isAssignableFrom(rawType)) {
// This covers a lot of Bukkit objects // This covers a lot of Bukkit objects
return (TypeAdapter<T>) new BukkitObjectTypeAdapter(gson.getAdapter(Map.class)); return (TypeAdapter<T>) new BukkitObjectTypeAdapter(gson.getAdapter(Map.class));

View File

@ -0,0 +1,50 @@
package world.bentobox.bentobox.database.json.adapters;
import java.io.IOException;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import world.bentobox.bentobox.util.Pair;
public class PairTypeAdapter<X, Z> extends TypeAdapter<Pair<X, Z>> {
private final Type xType;
private final Type zType;
public PairTypeAdapter(Type xType, Type zType) {
this.xType = xType;
this.zType = zType;
}
@Override
public void write(JsonWriter out, Pair<X, Z> pair) throws IOException {
out.beginObject();
out.name("x");
Gson gson = new Gson();
gson.toJson(pair.getKey(), xType, out);
out.name("z");
gson.toJson(pair.getValue(), zType, out);
out.endObject();
}
@Override
public Pair<X, Z> read(JsonReader in) throws IOException {
X x = null;
Z z = null;
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (name.equals("x")) {
x = new Gson().fromJson(in, xType);
} else if (name.equals("z")) {
z = new Gson().fromJson(in, zType);
}
}
in.endObject();
return new Pair<>(x, z);
}
}

View File

@ -3,6 +3,7 @@ package world.bentobox.bentobox.managers;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -30,7 +31,7 @@ public class PlayersManager {
private Database<Players> handler; private Database<Players> handler;
private final Database<Names> names; private final Database<Names> names;
private final Map<UUID, Players> playerCache = new ConcurrentHashMap<>(); private final Map<UUID, Players> playerCache = new ConcurrentHashMap<>();
private final @NonNull List<Names> nameCache;
private final Set<UUID> inTeleport; // this needs databasing private final Set<UUID> inTeleport; // this needs databasing
/** /**
@ -46,6 +47,7 @@ public class PlayersManager {
handler = new Database<>(plugin, Players.class); handler = new Database<>(plugin, Players.class);
// Set up the names database // Set up the names database
names = new Database<>(plugin, Names.class); names = new Database<>(plugin, Names.class);
nameCache = names.loadObjects();
inTeleport = new HashSet<>(); inTeleport = new HashSet<>();
} }
@ -139,7 +141,7 @@ public class PlayersManager {
// Not used // Not used
} }
} }
return names.loadObjects().stream().filter(n -> n.getUniqueId().equalsIgnoreCase(name)).findFirst() return nameCache.stream().filter(n -> n.getUniqueId().equalsIgnoreCase(name)).findFirst()
.map(Names::getUuid).orElse(null); .map(Names::getUuid).orElse(null);
} }
@ -148,10 +150,18 @@ public class PlayersManager {
* @param user - the User * @param user - the User
*/ */
public void setPlayerName(@NonNull User user) { public void setPlayerName(@NonNull User user) {
// Ignore any bots
if (user.getUniqueId() == null) {
return;
}
Players player = getPlayer(user.getUniqueId()); Players player = getPlayer(user.getUniqueId());
player.setPlayerName(user.getName()); player.setPlayerName(user.getName());
handler.saveObject(player); handler.saveObject(player);
// Update names
Names newName = new Names(user.getName(), user.getUniqueId()); Names newName = new Names(user.getName(), user.getUniqueId());
// Add to cache
nameCache.removeIf(name -> user.getUniqueId().equals(name.getUuid()));
nameCache.add(newName);
// Add to names database // Add to names database
names.saveObjectAsync(newName); names.saveObjectAsync(newName);
} }

View File

@ -0,0 +1,65 @@
package world.bentobox.bentobox.util;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* Fetches UUID for a player name from the Internet
* @since 1.24.1
*/
public class UUIDFetcher {
private static final String API_URL = "https://playerdb.co/api/player/minecraft/%s";
@Nullable
public static UUID getUUID(@NotNull String name) {
name = name.toLowerCase(); // Had some issues with upper-case letters in the username, so I added this to make sure that doesn't happen.
try {
HttpURLConnection connection = (HttpURLConnection) URI.create(String.format(API_URL, name)).toURL()
.openConnection();
connection.setUseCaches(false);
connection.setDefaultUseCaches(false);
connection.addRequestProperty("User-Agent", "Mozilla/5.0");
connection.addRequestProperty("Cache-Control", "no-cache, no-store, must-revalidate");
connection.addRequestProperty("Pragma", "no-cache");
connection.setReadTimeout(5000);
// These connection parameters need to be set or the API won't accept the connection.
try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null)
response.append(line);
final JsonElement parsed = JsonParser.parseString(response.toString());
if (parsed == null || !parsed.isJsonObject()) {
return null;
}
JsonObject data = parsed.getAsJsonObject(); // Read the returned JSON data.
return UUID.fromString(data.get("data").getAsJsonObject().get("player").getAsJsonObject().get("id") // Grab the UUID.
.getAsString());
}
} catch (Exception ignored) {
// Ignoring exception since this is usually caused by non-existent usernames.
}
return null;
}
}

View File

@ -9,6 +9,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -71,7 +72,7 @@ import world.bentobox.bentobox.util.Util;
* *
*/ */
@RunWith(PowerMockRunner.class) @RunWith(PowerMockRunner.class)
@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class, Logger.class, DatabaseSetup.class, }) @PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class, Logger.class, DatabaseSetup.class })
public class PlayersManagerTest { public class PlayersManagerTest {
private static AbstractDatabaseHandler<Object> handler; private static AbstractDatabaseHandler<Object> handler;
@ -685,12 +686,65 @@ public class PlayersManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.bentobox.managers.PlayersManager#getPlayer(java.util.UUID)}.
* {@link world.bentobox.bentobox.managers.PlayersManager#shutdown()}.
*/ */
@Test @Test
public void testShutdown() { public void testGetPlayer() {
pm.shutdown(); // Clears cache Players p = pm.getPlayer(uuid);
assertNotNull(p);
}
/**
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#loadPlayer(java.util.UUID)}.
*/
@Test
public void testLoadPlayer() {
assertNotNull(pm.loadPlayer(uuid));
}
/**
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#getName(java.util.UUID)}.
*/
@Test
public void testGetName() {
assertEquals("", pm.getName(uuid));
}
/**
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#cleanLeavingPlayer(org.bukkit.World, world.bentobox.bentobox.api.user.User, boolean, world.bentobox.bentobox.database.objects.Island)}.
*/
@Test
public void testCleanLeavingPlayer() {
when(user.isOnline()).thenReturn(true);
when(iwm.isOnLeaveResetEnderChest(world)).thenReturn(true);
when(iwm.isOnLeaveResetInventory(world)).thenReturn(true);
when(iwm.isOnLeaveResetMoney(world)).thenReturn(true);
pm.cleanLeavingPlayer(world, user, false, island);
PowerMockito.verifyStatic(Util.class);
Util.runCommands(user, "", plugin.getIWM().getOnLeaveCommands(world), "leave");
verify(world).getEntitiesByClass(Tameable.class);
verify(inv).clear(); // Enderchest cleared
verify(plugin).getVault(); // Clear money
PowerMockito.verifyStatic(Util.class);
Util.resetHealth(p);
verify(p).setFoodLevel(20);
verify(p).setLevel(0);
verify(p).setExp(0);
// Player total XP (not displayed)
verify(p).setTotalExperience(0);
}
/**
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#savePlayer(java.util.UUID)}.
* @throws IntrospectionException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Test
public void testSavePlayer() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
pm.savePlayer(uuid);
verify(handler, atLeastOnce()).saveObject(any());
} }
} }