From e4e93b1af1ef9db3b4dcfbb0d27b2940067dd924 Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 29 Mar 2018 21:46:34 +0100 Subject: [PATCH] Improve the way player uuid data is saved/stored. Add a warning message to catch ip forwarding issues --- .../api/delegates/model/ApiStorage.java | 6 +- .../commands/group/GroupListMembers.java | 2 +- .../common/commands/log/LogRecent.java | 2 +- .../common/commands/log/LogUserHistory.java | 2 +- .../common/commands/misc/SearchCommand.java | 2 +- .../common/commands/user/UserClone.java | 2 +- .../common/commands/user/UserInfo.java | 1 + .../common/commands/user/UserMainCommand.java | 4 +- .../luckperms/common/event/EventFactory.java | 8 +- .../common/event/impl/EventLogNotify.java | 4 +- ...ourceEntity.java => EntitySourceImpl.java} | 6 +- .../{EntitySender.java => SenderEntity.java} | 6 +- ...{SourceUnknown.java => UnknownSource.java} | 6 +- .../listener/AbstractConnectionListener.java | 12 +- .../common/locale/message/Message.java | 2 +- .../common/storage/AbstractStorage.java | 12 +- .../common/storage/PlayerSaveResult.java | 194 ++++++++++++++++++ .../luckperms/common/storage/Storage.java | 6 +- .../common/storage/dao/AbstractDao.java | 7 +- .../common/storage/dao/SplitStorageDao.java | 13 +- .../storage/dao/file/ConfigurateDao.java | 11 +- .../storage/dao/file/FileUuidCache.java | 185 ++++++++++------- .../common/storage/dao/mongodb/MongoDao.java | 50 +++-- .../common/storage/dao/sql/SqlDao.java | 163 ++++++++------- .../lucko/luckperms/common/utils/Uuids.java | 2 +- 25 files changed, 485 insertions(+), 223 deletions(-) rename common/src/main/java/me/lucko/luckperms/common/event/model/{SourceEntity.java => EntitySourceImpl.java} (91%) rename common/src/main/java/me/lucko/luckperms/common/event/model/{EntitySender.java => SenderEntity.java} (92%) rename common/src/main/java/me/lucko/luckperms/common/event/model/{SourceUnknown.java => UnknownSource.java} (91%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java index 3c7dc0b9..f1a837c3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java @@ -254,7 +254,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage { public CompletableFuture saveUUIDData(@Nonnull String username, @Nonnull UUID uuid) { Objects.requireNonNull(username, "username"); Objects.requireNonNull(uuid, "uuid"); - return this.handle.noBuffer().saveUUIDData(uuid, checkUsername(username)) + return this.handle.noBuffer().savePlayerData(uuid, checkUsername(username)) .thenApply(r -> true) .exceptionally(consumeExceptionToFalse()); } @@ -263,13 +263,13 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage { @Override public CompletableFuture getUUID(@Nonnull String username) { Objects.requireNonNull(username, "username"); - return this.handle.noBuffer().getUUID(checkUsername(username)); + return this.handle.noBuffer().getPlayerUuid(checkUsername(username)); } @Nonnull @Override public CompletableFuture getName(@Nonnull UUID uuid) { Objects.requireNonNull(uuid, "uuid"); - return this.handle.noBuffer().getName(uuid); + return this.handle.noBuffer().getPlayerName(uuid); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java index 2ebe9529..dd9a640b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java @@ -92,7 +92,7 @@ public class GroupListMembers extends SubCommand { if (!matchedUsers.isEmpty()) { LoadingCache uuidLookups = Caffeine.newBuilder() .build(u -> { - String s = plugin.getStorage().getName(u).join(); + String s = plugin.getStorage().getPlayerName(u).join(); if (s == null || s.isEmpty() || s.equals("null")) { s = u.toString(); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java index d26057ed..ed97ed21 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java @@ -86,7 +86,7 @@ public class LogRecent extends SubCommand { } } - uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); + uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join(); if (uuid == null) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { Message.USER_NOT_FOUND.send(sender, target); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java index 408b4cda..b60cbce6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java @@ -81,7 +81,7 @@ public class LogUserHistory extends SubCommand { } } - uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); + uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join(); if (uuid == null) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { Message.USER_NOT_FOUND.send(sender, target); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java index e57cbd83..898000d3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java @@ -86,7 +86,7 @@ public class SearchCommand extends SingleCommand { if (!matchedUsers.isEmpty()) { LoadingCache uuidLookups = Caffeine.newBuilder() .build(u -> { - String s = plugin.getStorage().getName(u).join(); + String s = plugin.getStorage().getPlayerName(u).join(); if (s == null || s.isEmpty() || s.equals("null")) { s = u.toString(); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserClone.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserClone.java index 5b55ef8a..53b947b8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserClone.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserClone.java @@ -73,7 +73,7 @@ public class UserClone extends SubCommand { } } - uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); + uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join(); if (uuid == null) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { Message.USER_NOT_FOUND.send(sender, target); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java index 4977b319..402b3ad4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java @@ -67,6 +67,7 @@ public class UserInfo extends SubCommand { Message.USER_INFO_GENERAL.send(sender, user.getName().orElse("Unknown"), user.getUuid(), + user.getUuid().version() == 4 ? "&2mojang" : "&8offline", status.asString(plugin.getLocaleManager()), user.getPrimaryGroup().getValue() ); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java index a6953198..6921d385 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java @@ -96,7 +96,7 @@ public class UserMainCommand extends MainCommand { } } - uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); + uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join(); if (uuid == null) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { Message.USER_NOT_FOUND.send(sender, target); @@ -111,7 +111,7 @@ public class UserMainCommand extends MainCommand { } } - String name = plugin.getStorage().getName(uuid).join(); + String name = plugin.getStorage().getPlayerName(uuid).join(); return UserIdentifier.of(uuid, name); } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java b/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java index b8eddff7..a0236e96 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java @@ -72,8 +72,8 @@ import me.lucko.luckperms.common.event.impl.EventUserFirstLogin; import me.lucko.luckperms.common.event.impl.EventUserLoad; import me.lucko.luckperms.common.event.impl.EventUserLoginProcess; import me.lucko.luckperms.common.event.impl.EventUserPromote; -import me.lucko.luckperms.common.event.model.EntitySender; -import me.lucko.luckperms.common.event.model.SourceEntity; +import me.lucko.luckperms.common.event.model.EntitySourceImpl; +import me.lucko.luckperms.common.event.model.SenderEntity; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.Track; @@ -268,12 +268,12 @@ public final class EventFactory { } public void handleUserDemote(User user, Track track, String from, String to, Sender source) { - EventUserDemote event = new EventUserDemote(track.getApiDelegate(), new ApiUser(user), from, to, new SourceEntity(new EntitySender(source))); + EventUserDemote event = new EventUserDemote(track.getApiDelegate(), new ApiUser(user), from, to, new EntitySourceImpl(new SenderEntity(source))); fireEventAsync(event); } public void handleUserPromote(User user, Track track, String from, String to, Sender source) { - EventUserPromote event = new EventUserPromote(track.getApiDelegate(), new ApiUser(user), from, to, new SourceEntity(new EntitySender(source))); + EventUserPromote event = new EventUserPromote(track.getApiDelegate(), new ApiUser(user), from, to, new EntitySourceImpl(new SenderEntity(source))); fireEventAsync(event); } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/impl/EventLogNotify.java b/common/src/main/java/me/lucko/luckperms/common/event/impl/EventLogNotify.java index 6ce1a7db..31851476 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/impl/EventLogNotify.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/impl/EventLogNotify.java @@ -29,7 +29,7 @@ import me.lucko.luckperms.api.Entity; import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.event.log.LogNotifyEvent; import me.lucko.luckperms.common.event.AbstractEvent; -import me.lucko.luckperms.common.event.model.EntitySender; +import me.lucko.luckperms.common.event.model.SenderEntity; import me.lucko.luckperms.common.sender.Sender; import java.util.concurrent.atomic.AtomicBoolean; @@ -56,7 +56,7 @@ public class EventLogNotify extends AbstractEvent implements LogNotifyEvent { @Override public synchronized Entity getNotifiable() { if (this.notifiable == null) { - this.notifiable = new EntitySender(this.sender); + this.notifiable = new SenderEntity(this.sender); } return this.notifiable; } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/model/SourceEntity.java b/common/src/main/java/me/lucko/luckperms/common/event/model/EntitySourceImpl.java similarity index 91% rename from common/src/main/java/me/lucko/luckperms/common/event/model/SourceEntity.java rename to common/src/main/java/me/lucko/luckperms/common/event/model/EntitySourceImpl.java index 67352c7b..684dce43 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/model/SourceEntity.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/model/EntitySourceImpl.java @@ -30,10 +30,10 @@ import me.lucko.luckperms.api.event.source.EntitySource; import javax.annotation.Nonnull; -public class SourceEntity implements EntitySource { +public class EntitySourceImpl implements EntitySource { private final Entity entity; - public SourceEntity(Entity entity) { + public EntitySourceImpl(Entity entity) { this.entity = entity; } @@ -51,6 +51,6 @@ public class SourceEntity implements EntitySource { @Override public String toString() { - return "Sender(type=ENTITY, entity=" + this.entity + ")"; + return "Source(type=ENTITY, entity=" + this.entity + ")"; } } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/model/EntitySender.java b/common/src/main/java/me/lucko/luckperms/common/event/model/SenderEntity.java similarity index 92% rename from common/src/main/java/me/lucko/luckperms/common/event/model/EntitySender.java rename to common/src/main/java/me/lucko/luckperms/common/event/model/SenderEntity.java index 3a90eb35..ce923559 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/model/EntitySender.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/model/SenderEntity.java @@ -33,10 +33,10 @@ import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class EntitySender implements Entity { +public class SenderEntity implements Entity { private final Sender sender; - public EntitySender(Sender sender) { + public SenderEntity(Sender sender) { this.sender = sender; } @@ -67,6 +67,6 @@ public class EntitySender implements Entity { @Override public String toString() { - return "Sender(type=" + getType() + ", sender=" + this.sender + ")"; + return "SenderEntity(type=" + getType() + ", sender=" + this.sender + ")"; } } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/model/SourceUnknown.java b/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java similarity index 91% rename from common/src/main/java/me/lucko/luckperms/common/event/model/SourceUnknown.java rename to common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java index 3e985bbf..d5440f7e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/model/SourceUnknown.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java @@ -29,10 +29,10 @@ import me.lucko.luckperms.api.event.source.Source; import javax.annotation.Nonnull; -public final class SourceUnknown implements Source { - private static final Source INSTANCE = new SourceUnknown(); +public final class UnknownSource implements Source { + private static final Source INSTANCE = new UnknownSource(); - private SourceUnknown() { + private UnknownSource() { } diff --git a/common/src/main/java/me/lucko/luckperms/common/listener/AbstractConnectionListener.java b/common/src/main/java/me/lucko/luckperms/common/listener/AbstractConnectionListener.java index 263260c7..f1597593 100644 --- a/common/src/main/java/me/lucko/luckperms/common/listener/AbstractConnectionListener.java +++ b/common/src/main/java/me/lucko/luckperms/common/listener/AbstractConnectionListener.java @@ -29,6 +29,7 @@ import me.lucko.luckperms.common.assignments.AssignmentRule; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.storage.PlayerSaveResult; import java.util.Set; import java.util.UUID; @@ -61,11 +62,16 @@ public abstract class AbstractConnectionListener implements ConnectionListener { this.plugin.getUserManager().getHouseKeeper().registerUsage(u); // save uuid data. - String name = this.plugin.getStorage().noBuffer().getName(u).join(); - if (name == null) { + PlayerSaveResult saveResult = this.plugin.getStorage().savePlayerData(u, username).join(); + if (saveResult.includes(PlayerSaveResult.Status.CLEAN_INSERT)) { this.plugin.getEventFactory().handleUserFirstLogin(u, username); } - this.plugin.getStorage().noBuffer().saveUUIDData(u, username); + + if (saveResult.includes(PlayerSaveResult.Status.OTHER_UUIDS_PRESENT_FOR_USERNAME)) { + this.plugin.getLogger().warn("LuckPerms already has data for player '" + username + "' - but this data is stored under a different uuid."); + this.plugin.getLogger().warn("'" + username + "' has previously used the unique ids " + saveResult.getOtherUuids() + " but is now connecting with '" + u + "'"); + this.plugin.getLogger().warn("This is usually because the server is not authenticating correctly. If you're using BungeeCord, please ensure that IP-Forwarding is setup correctly!"); + } User user = this.plugin.getStorage().noBuffer().loadUser(u, username).join(); if (user == null) { diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java index 47cf0b40..219f4fc0 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java @@ -309,7 +309,7 @@ public enum Message { USER_INFO_GENERAL( "{PREFIX}&b&l> &bUser Info: &f{}" + "\n" + - "{PREFIX}&f- &3UUID: &f{}" + "\n" + + "{PREFIX}&f- &3UUID: &f{} &7(type: {}&7)" + "\n" + "{PREFIX}&f- &3Status: {}" + "\n" + "{PREFIX}&f- &3Primary Group: &f{}", false diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java index 021c10fb..c0a072df 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java @@ -290,17 +290,17 @@ public class AbstractStorage implements Storage { } @Override - public CompletableFuture saveUUIDData(UUID uuid, String username) { - return makeFuture(() -> this.dao.saveUUIDData(uuid, username)); + public CompletableFuture savePlayerData(UUID uuid, String username) { + return makeFuture(() -> this.dao.savePlayerData(uuid, username)); } @Override - public CompletableFuture getUUID(String username) { - return makeFuture(() -> this.dao.getUUID(username)); + public CompletableFuture getPlayerUuid(String username) { + return makeFuture(() -> this.dao.getPlayerUuid(username)); } @Override - public CompletableFuture getName(UUID uuid) { - return makeFuture(() -> this.dao.getName(uuid)); + public CompletableFuture getPlayerName(UUID uuid) { + return makeFuture(() -> this.dao.getPlayerName(uuid)); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java b/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java new file mode 100644 index 00000000..7d883fcd --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java @@ -0,0 +1,194 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.storage; + +import com.google.common.collect.ImmutableSet; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Represents the result to a player history save operation + */ +public final class PlayerSaveResult { + private static final PlayerSaveResult CLEAN_INSERT = new PlayerSaveResult(Status.CLEAN_INSERT); + private static final PlayerSaveResult NO_CHANGE = new PlayerSaveResult(Status.NO_CHANGE); + + public static PlayerSaveResult cleanInsert() { + return CLEAN_INSERT; + } + + public static PlayerSaveResult noChange() { + return NO_CHANGE; + } + + public static PlayerSaveResult usernameUpdated(String oldUsername) { + return new PlayerSaveResult(oldUsername, null, Status.USERNAME_UPDATED); + } + + public static PlayerSaveResult determineBaseResult(String username, String oldUsername) { + PlayerSaveResult result; + if (oldUsername == null) { + result = PlayerSaveResult.cleanInsert(); + } else if (oldUsername.equalsIgnoreCase(username)) { + result = PlayerSaveResult.noChange(); + } else { + result = PlayerSaveResult.usernameUpdated(oldUsername); + } + return result; + } + + private final Set status; + @Nullable private final String oldUsername; + @Nullable private final Set otherUuids; + + private PlayerSaveResult(EnumSet status, @Nullable String oldUsername, @Nullable Set otherUuids) { + this.status = ImmutableSet.copyOf(status); + this.oldUsername = oldUsername; + this.otherUuids = otherUuids; + } + + private PlayerSaveResult(@Nullable String oldUsername, @Nullable Set otherUuids, Status... status) { + this(EnumSet.copyOf(Arrays.asList(status)), oldUsername, otherUuids); + } + + private PlayerSaveResult(Status... status) { + this(null, null, status); + } + + /** + * Returns a new {@link PlayerSaveResult} with the {@link Status#OTHER_UUIDS_PRESENT_FOR_USERNAME} + * status attached to the state of this result. + * + * @param otherUuids the other uuids + * @return a new result + */ + public PlayerSaveResult withOtherUuidsPresent(@Nonnull Set otherUuids) { + EnumSet status = EnumSet.copyOf(this.status); + status.add(Status.OTHER_UUIDS_PRESENT_FOR_USERNAME); + return new PlayerSaveResult(status, this.oldUsername, ImmutableSet.copyOf(otherUuids)); + } + + /** + * Gets the status returned by the operation + * + * @return the status + */ + public Set getStatus() { + return this.status; + } + + /** + * Gets if the result includes a certain status code. + * + * @param status the status to check for + * @return if the result includes the status + */ + public boolean includes(Status status) { + return this.status.contains(status); + } + + /** + * Gets the old username involved in the result + * + * @return the old username + * @see Status#USERNAME_UPDATED + */ + @Nullable + public String getOldUsername() { + return this.oldUsername; + } + + /** + * Gets the other uuids involved in the result + * + * @return the other uuids + * @see Status#OTHER_UUIDS_PRESENT_FOR_USERNAME + */ + @Nullable + public Set getOtherUuids() { + return this.otherUuids; + } + + @Override + public boolean equals(Object that) { + if (this == that) return true; + if (that == null || getClass() != that.getClass()) return false; + PlayerSaveResult result = (PlayerSaveResult) that; + return Objects.equals(this.status, result.status) && + Objects.equals(this.oldUsername, result.oldUsername) && + Objects.equals(this.otherUuids, result.otherUuids); + } + + @Override + public int hashCode() { + return Objects.hash(this.status, this.oldUsername, this.otherUuids); + } + + /** + * The various states the result can take + */ + public enum Status { + + /** + * There was no existing data saved for either the uuid or username + */ + CLEAN_INSERT, + + /** + * There was existing data for the player, no change was needed. + */ + NO_CHANGE, + + /** + * There was already a record for the UUID saved, but it was for a different username. + * + *

This is normal, players are able to change their usernames.

+ */ + USERNAME_UPDATED, + + /** + * There was already a record for the username saved, but it was under a different uuid. + * + *

This is a bit of a cause for concern. It's possible that "player1" has changed + * their username to "player2", and "player3" has changed their username to "player1". + * If the original "player1" doesn't join after changing their name, this conflict could + * occur.

+ * + *

However, what's more likely is that the server is not setup to authenticate + * correctly. Usually this is a problem with BungeeCord "ip-forwarding", but could be + * that the user of the plugin is running a network off a shared database with one + * server in online mode and another in offline mode.

+ */ + OTHER_UUIDS_PRESENT_FOR_USERNAME, + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java b/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java index 5974a065..58f01863 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java @@ -99,9 +99,9 @@ public interface Storage { CompletableFuture deleteTrack(Track track, DeletionCause cause); - CompletableFuture saveUUIDData(UUID uuid, String username); + CompletableFuture savePlayerData(UUID uuid, String username); - CompletableFuture getUUID(String username); + CompletableFuture getPlayerUuid(String username); - CompletableFuture getName(UUID uuid); + CompletableFuture getPlayerName(UUID uuid); } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/AbstractDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/AbstractDao.java index 01f3e434..54c5901d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/AbstractDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/AbstractDao.java @@ -33,6 +33,7 @@ import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.storage.PlayerSaveResult; import java.util.Collections; import java.util.List; @@ -105,12 +106,12 @@ public abstract class AbstractDao { public abstract void deleteTrack(Track track) throws Exception; - public abstract void saveUUIDData(UUID uuid, String username) throws Exception; + public abstract PlayerSaveResult savePlayerData(UUID uuid, String username) throws Exception; @Nullable - public abstract UUID getUUID(String username) throws Exception; + public abstract UUID getPlayerUuid(String username) throws Exception; @Nullable - public abstract String getName(UUID uuid) throws Exception; + public abstract String getPlayerName(UUID uuid) throws Exception; } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/SplitStorageDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/SplitStorageDao.java index 7d4df1bf..30502efc 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/SplitStorageDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/SplitStorageDao.java @@ -35,6 +35,7 @@ import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.storage.PlayerSaveResult; import me.lucko.luckperms.common.storage.SplitStorageType; import me.lucko.luckperms.common.storage.StorageType; @@ -191,17 +192,17 @@ public class SplitStorageDao extends AbstractDao { } @Override - public void saveUUIDData(UUID uuid, String username) throws Exception { - this.backing.get(this.types.get(SplitStorageType.UUID)).saveUUIDData(uuid, username); + public PlayerSaveResult savePlayerData(UUID uuid, String username) throws Exception { + return this.backing.get(this.types.get(SplitStorageType.UUID)).savePlayerData(uuid, username); } @Override - public UUID getUUID(String username) throws Exception { - return this.backing.get(this.types.get(SplitStorageType.UUID)).getUUID(username); + public UUID getPlayerUuid(String username) throws Exception { + return this.backing.get(this.types.get(SplitStorageType.UUID)).getPlayerUuid(username); } @Override - public String getName(UUID uuid) throws Exception { - return this.backing.get(this.types.get(SplitStorageType.UUID)).getName(uuid); + public String getPlayerName(UUID uuid) throws Exception { + return this.backing.get(this.types.get(SplitStorageType.UUID)).getPlayerName(uuid); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java index fcef7139..9db3eae9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java @@ -47,6 +47,7 @@ import me.lucko.luckperms.common.node.NodeHeldPermission; import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.references.UserIdentifier; +import me.lucko.luckperms.common.storage.PlayerSaveResult; import me.lucko.luckperms.common.storage.dao.AbstractDao; import me.lucko.luckperms.common.utils.ImmutableCollectors; import me.lucko.luckperms.common.utils.Uuids; @@ -670,17 +671,17 @@ public abstract class ConfigurateDao extends AbstractDao { } @Override - public void saveUUIDData(UUID uuid, String username) { - this.uuidCache.addMapping(uuid, username); + public PlayerSaveResult savePlayerData(UUID uuid, String username) { + return this.uuidCache.addMapping(uuid, username); } @Override - public UUID getUUID(String username) { - return this.uuidCache.lookup(username); + public UUID getPlayerUuid(String username) { + return this.uuidCache.lookupUuid(username); } @Override - public String getName(UUID uuid) { + public String getPlayerName(UUID uuid) { return this.uuidCache.lookupUsername(uuid); } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/FileUuidCache.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/FileUuidCache.java index 786f24c2..064e20d8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/FileUuidCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/FileUuidCache.java @@ -26,9 +26,12 @@ package me.lucko.luckperms.common.storage.dao.file; import com.google.common.base.Splitter; -import com.google.common.collect.Maps; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; -import me.lucko.luckperms.common.utils.DateUtil; +import me.lucko.luckperms.common.storage.PlayerSaveResult; +import me.lucko.luckperms.common.utils.Uuids; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -36,19 +39,60 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; public class FileUuidCache { - private static final Splitter KV_SPLIT = Splitter.on('=').omitEmptyStrings(); - private static final Splitter TIME_SPLIT = Splitter.on('|').omitEmptyStrings(); + private static final Splitter KV_SPLIT = Splitter.on(':').omitEmptyStrings(); + private static final Splitter LEGACY_KV_SPLIT = Splitter.on('=').omitEmptyStrings(); + private static final Splitter LEGACY_TIME_SPLIT = Splitter.on('|').omitEmptyStrings(); - // the map for lookups - private final Map> lookupMap = new ConcurrentHashMap<>(); + // the lookup map + private final LookupMap lookupMap = new LookupMap(); + + private static final class LookupMap extends ConcurrentHashMap { + private final SetMultimap reverse = Multimaps.newSetMultimap(new ConcurrentHashMap<>(), ConcurrentHashMap::newKeySet); + + @Override + public String put(UUID key, String value) { + String existing = super.put(key, value); + + // check if we need to remove a reverse entry which has been replaced + // existing might be null + if (!value.equalsIgnoreCase(existing)) { + if (existing != null) { + this.reverse.remove(existing.toLowerCase(), key); + } + } + + this.reverse.put(value.toLowerCase(), key); + return existing; + } + + @Override + public String remove(Object k) { + UUID key = (UUID) k; + String username = super.remove(key); + if (username != null) { + this.reverse.remove(username.toLowerCase(), key); + } + return username; + } + + public String lookupUsername(UUID uuid) { + return super.get(uuid); + } + + public Set lookupUuid(String name) { + return this.reverse.get(name.toLowerCase()); + } + } /** * Adds a mapping to the cache @@ -56,8 +100,25 @@ public class FileUuidCache { * @param uuid the uuid of the player * @param username the username of the player */ - public void addMapping(UUID uuid, String username) { - this.lookupMap.put(username.toLowerCase(), Maps.immutableEntry(uuid, DateUtil.unixSecondsNow())); + public PlayerSaveResult addMapping(UUID uuid, String username) { + // perform the insert + String oldUsername = this.lookupMap.put(uuid, username); + + PlayerSaveResult result = PlayerSaveResult.determineBaseResult(username, oldUsername); + + Set conflicting = new HashSet<>(this.lookupMap.lookupUuid(username)); + conflicting.remove(uuid); + + if (!conflicting.isEmpty()) { + // remove the mappings for conflicting uuids + for (UUID conflict : conflicting) { + this.lookupMap.remove(conflict); + } + + result = result.withOtherUuidsPresent(conflicting); + } + + return result; } /** @@ -67,9 +128,9 @@ public class FileUuidCache { * @return a uuid, or null */ @Nullable - public UUID lookup(String username) { - Map.Entry ret = this.lookupMap.get(username.toLowerCase()); - return ret == null ? null : ret.getKey(); + public UUID lookupUuid(String username) { + Set uuids = this.lookupMap.lookupUuid(username); + return Iterables.getFirst(uuids, null); } /** @@ -79,23 +140,46 @@ public class FileUuidCache { * @return a username, or null */ public String lookupUsername(UUID uuid) { - String username = null; - Long time = Long.MIN_VALUE; + return this.lookupMap.lookupUsername(uuid); + } - for (Map.Entry> ent : this.lookupMap.entrySet()) { - if (!ent.getValue().getKey().equals(uuid)) { - continue; + private void loadEntry(String entry) { + if (entry.contains(":")) { + // new format + Iterator parts = KV_SPLIT.split(entry).iterator(); + + if (!parts.hasNext()) return; + String uuidPart = parts.next(); + + if (!parts.hasNext()) return; + String usernamePart = parts.next(); + + UUID uuid = Uuids.fromString(uuidPart); + if (uuid == null) return; + + this.lookupMap.put(uuid, usernamePart); + } else if (entry.contains("=")) { + // old format + Iterator parts = LEGACY_KV_SPLIT.split(entry).iterator(); + + if (!parts.hasNext()) return; + String usernamePart = parts.next(); + + if (!parts.hasNext()) return; + String uuidPart = parts.next(); + + // contains a time + if (uuidPart.contains("|")) { + Iterator valueParts = LEGACY_TIME_SPLIT.split(uuidPart).iterator(); + if (!valueParts.hasNext()) return; + uuidPart = valueParts.next(); } - Long t = ent.getValue().getValue(); + UUID uuid = Uuids.fromString(uuidPart); + if (uuid == null) return; - if (t > time) { - time = t; - username = ent.getKey(); - } + this.lookupMap.put(uuid, usernamePart); } - - return username; } public void load(File file) { @@ -104,61 +188,14 @@ public class FileUuidCache { } try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { - String entry; while ((entry = reader.readLine()) != null) { entry = entry.trim(); if (entry.isEmpty() || entry.startsWith("#")) { continue; } - - Iterator parts = KV_SPLIT.split(entry).iterator(); - - if (!parts.hasNext()) continue; - String key = parts.next(); - - if (!parts.hasNext()) continue; - String value = parts.next(); - - UUID uid; - Long t; - - // contains a time (backwards compat) - if (value.contains("|")) { - // try to split and extract the time element from the end. - Iterator valueParts = TIME_SPLIT.split(value).iterator(); - - if (!valueParts.hasNext()) continue; - String uuid = valueParts.next(); - - if (!valueParts.hasNext()) continue; - String time = valueParts.next(); - - try { - uid = UUID.fromString(uuid); - } catch (IllegalArgumentException e) { - continue; - } - - try { - t = Long.parseLong(time); - } catch (NumberFormatException e) { - continue; - } - } else { - // just parse from the value - try { - uid = UUID.fromString(value); - } catch (IllegalArgumentException e) { - continue; - } - - t = 0L; - } - - this.lookupMap.put(key, Maps.immutableEntry(uid, t)); + loadEntry(entry); } - } catch (IOException e) { e.printStackTrace(); } @@ -168,13 +205,11 @@ public class FileUuidCache { try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { writer.write("# LuckPerms UUID lookup cache"); writer.newLine(); - - for (Map.Entry> ent : this.lookupMap.entrySet()) { - String out = ent.getKey() + "=" + ent.getValue().getKey().toString() + "|" + ent.getValue().getValue().toString(); + for (Map.Entry ent : this.lookupMap.entrySet()) { + String out = ent.getKey() + ":" + ent.getValue(); writer.write(out); writer.newLine(); } - writer.flush(); } catch (IOException e) { e.printStackTrace(); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java index 127f1062..36d5afd5 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java @@ -34,6 +34,7 @@ import com.mongodb.ServerAddress; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; import com.mongodb.client.model.UpdateOptions; import me.lucko.luckperms.api.HeldPermission; @@ -56,6 +57,7 @@ import me.lucko.luckperms.common.node.NodeHeldPermission; import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.references.UserIdentifier; +import me.lucko.luckperms.common.storage.PlayerSaveResult; import me.lucko.luckperms.common.storage.StorageCredentials; import me.lucko.luckperms.common.storage.dao.AbstractDao; @@ -569,29 +571,53 @@ public class MongoDao extends AbstractDao { } @Override - public void saveUUIDData(UUID uuid, String username) { + public PlayerSaveResult savePlayerData(UUID uuid, String username) { + username = username.toLowerCase(); MongoCollection c = this.database.getCollection(this.prefix + "uuid"); - c.replaceOne(new Document("_id", uuid), new Document("_id", uuid).append("name", username.toLowerCase()), new UpdateOptions().upsert(true)); + + // find any existing mapping + String oldUsername = getPlayerName(uuid); + + // do the insert + if (!username.equalsIgnoreCase(oldUsername)) { + c.replaceOne(new Document("_id", uuid), new Document("_id", uuid).append("name", username), new UpdateOptions().upsert(true)); + } + + PlayerSaveResult result = PlayerSaveResult.determineBaseResult(username, oldUsername); + + Set conflicting = new HashSet<>(); + try (MongoCursor cursor = c.find(new Document("name", username)).iterator()) { + if (cursor.hasNext()) { + conflicting.add(cursor.next().get("_id", UUID.class)); + } + } + conflicting.remove(uuid); + + if (!conflicting.isEmpty()) { + // remove the mappings for conflicting uuids + c.deleteMany(Filters.and(conflicting.stream().map(u -> Filters.eq("_id", u)).collect(Collectors.toList()))); + result = result.withOtherUuidsPresent(conflicting); + } + + return result; } @Override - public UUID getUUID(String username) { + public UUID getPlayerUuid(String username) { MongoCollection c = this.database.getCollection(this.prefix + "uuid"); - try (MongoCursor cursor = c.find(new Document("name", username.toLowerCase())).iterator()) { - if (cursor.hasNext()) { - return cursor.next().get("_id", UUID.class); - } + Document doc = c.find(new Document("name", username.toLowerCase())).first(); + if (doc != null) { + return doc.get("_id", UUID.class); } return null; } @Override - public String getName(UUID uuid) { + public String getPlayerName(UUID uuid) { MongoCollection c = this.database.getCollection(this.prefix + "uuid"); - try (MongoCursor cursor = c.find(new Document("_id", uuid)).iterator()) { - if (cursor.hasNext()) { - return cursor.next().get("name", String.class); - } + Document doc = c.find(new Document("_id", uuid)).first(); + if (doc != null) { + return doc.get("name", String.class); } return null; } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java index b5f25bca..428d7b82 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java @@ -46,6 +46,7 @@ import me.lucko.luckperms.common.node.NodeHeldPermission; import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.references.UserIdentifier; +import me.lucko.luckperms.common.storage.PlayerSaveResult; import me.lucko.luckperms.common.storage.dao.AbstractDao; import me.lucko.luckperms.common.storage.dao.sql.connection.AbstractConnectionFactory; import me.lucko.luckperms.common.storage.dao.sql.connection.file.SQLiteConnectionFactory; @@ -68,8 +69,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; @@ -83,14 +82,15 @@ public class SqlDao extends AbstractDao { private static final String USER_PERMISSIONS_SELECT_DISTINCT = "SELECT DISTINCT uuid FROM {prefix}user_permissions"; private static final String USER_PERMISSIONS_SELECT_PERMISSION = "SELECT uuid, value, server, world, expiry, contexts FROM {prefix}user_permissions WHERE permission=?"; - private static final String PLAYER_SELECT = "SELECT username, primary_group FROM {prefix}players WHERE uuid=?"; - private static final String PLAYER_SELECT_UUID = "SELECT uuid FROM {prefix}players WHERE username=? LIMIT 1"; - private static final String PLAYER_SELECT_USERNAME = "SELECT username FROM {prefix}players WHERE uuid=? LIMIT 1"; - private static final String PLAYER_SELECT_PRIMARY_GROUP = "SELECT primary_group FROM {prefix}players WHERE uuid=? LIMIT 1"; + private static final String PLAYER_SELECT_UUID_BY_USERNAME = "SELECT uuid FROM {prefix}players WHERE username=? LIMIT 1"; + private static final String PLAYER_SELECT_USERNAME_BY_UUID = "SELECT username FROM {prefix}players WHERE uuid=? LIMIT 1"; + private static final String PLAYER_UPDATE_USERNAME_FOR_UUID = "UPDATE {prefix}players SET username=? WHERE uuid=?"; private static final String PLAYER_INSERT = "INSERT INTO {prefix}players VALUES(?, ?, ?)"; - private static final String PLAYER_UPDATE = "UPDATE {prefix}players SET username=? WHERE uuid=?"; - private static final String PLAYER_DELETE = "DELETE FROM {prefix}players WHERE username=? AND NOT uuid=?"; - private static final String PLAYER_UPDATE_PRIMARY_GROUP = "UPDATE {prefix}players SET primary_group=? WHERE uuid=?"; + private static final String PLAYER_SELECT_ALL_UUIDS_BY_USERNAME = "SELECT uuid FROM {prefix}players WHERE username=? AND NOT uuid=?"; + private static final String PLAYER_DELETE_ALL_UUIDS_BY_USERNAME = "DELETE FROM {prefix}players WHERE username=? AND NOT uuid=?"; + private static final String PLAYER_SELECT_BY_UUID = "SELECT username, primary_group FROM {prefix}players WHERE uuid=?"; + private static final String PLAYER_SELECT_PRIMARY_GROUP_BY_UUID = "SELECT primary_group FROM {prefix}players WHERE uuid=? LIMIT 1"; + private static final String PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID = "UPDATE {prefix}players SET primary_group=? WHERE uuid=?"; private static final String GROUP_PERMISSIONS_SELECT = "SELECT permission, value, server, world, expiry, contexts FROM {prefix}group_permissions WHERE name=?"; private static final String GROUP_PERMISSIONS_DELETE = "DELETE FROM {prefix}group_permissions WHERE name=?"; @@ -295,8 +295,8 @@ public class SqlDao extends AbstractDao { user.getIoLock().lock(); try { List data = new ArrayList<>(); - AtomicReference primaryGroup = new AtomicReference<>(null); - AtomicReference userName = new AtomicReference<>(null); + String primaryGroup = null; + String userName = null; // Collect user permissions try (Connection c = this.provider.getConnection()) { @@ -319,27 +319,26 @@ public class SqlDao extends AbstractDao { // Collect user meta (username & primary group) try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT))) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_BY_UUID))) { ps.setString(1, user.getUuid().toString()); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { - userName.set(rs.getString("username")); - primaryGroup.set(rs.getString("primary_group")); + userName = rs.getString("username"); + primaryGroup = rs.getString("primary_group"); } } } } // update username & primary group - String pg = primaryGroup.get(); - if (pg == null) { - pg = NodeFactory.DEFAULT_GROUP_NAME; + if (primaryGroup == null) { + primaryGroup = NodeFactory.DEFAULT_GROUP_NAME; } - user.getPrimaryGroup().setStoredValue(pg); + user.getPrimaryGroup().setStoredValue(primaryGroup); // Update their username to what was in the storage if the one in the local instance is null - user.setName(userName.get(), true); + user.setName(userName, true); // If the user has any data in storage if (!data.isEmpty()) { @@ -378,7 +377,7 @@ public class SqlDao extends AbstractDao { ps.setString(1, user.getUuid().toString()); ps.execute(); } - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE_PRIMARY_GROUP))) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) { ps.setString(1, NodeFactory.DEFAULT_GROUP_NAME); ps.setString(2, user.getUuid().toString()); ps.execute(); @@ -453,7 +452,7 @@ public class SqlDao extends AbstractDao { try (Connection c = this.provider.getConnection()) { boolean hasPrimaryGroupSaved; - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_PRIMARY_GROUP))) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) { ps.setString(1, user.getUuid().toString()); try (ResultSet rs = ps.executeQuery()) { hasPrimaryGroupSaved = rs.next(); @@ -462,7 +461,7 @@ public class SqlDao extends AbstractDao { if (hasPrimaryGroupSaved) { // update - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE_PRIMARY_GROUP))) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) { ps.setString(1, user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME)); ps.setString(2, user.getUuid().toString()); ps.execute(); @@ -772,24 +771,23 @@ public class SqlDao extends AbstractDao { Track track = this.plugin.getTrackManager().getOrMake(name); track.getIoLock().lock(); try { - AtomicBoolean exists = new AtomicBoolean(false); - AtomicReference groups = new AtomicReference<>(null); - + boolean exists = false; + String groups = null; try (Connection c = this.provider.getConnection()) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(TRACK_SELECT))) { ps.setString(1, track.getName()); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { - exists.set(true); - groups.set(rs.getString("groups")); + exists = true; + groups = rs.getString("groups"); } } } } - if (exists.get()) { + if (exists) { // Track exists, let's load. - track.setGroups(this.gson.fromJson(groups.get(), LIST_STRING_TYPE)); + track.setGroups(this.gson.fromJson(groups, LIST_STRING_TYPE)); } else { String json = this.gson.toJson(track.getGroups()); try (Connection c = this.provider.getConnection()) { @@ -813,14 +811,13 @@ public class SqlDao extends AbstractDao { track.getIoLock().lock(); } try { - AtomicReference groups = new AtomicReference<>(null); - + String groups; try (Connection c = this.provider.getConnection()) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(TRACK_SELECT))) { ps.setString(1, name); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { - groups.set(rs.getString("groups")); + groups = rs.getString("groups"); } else { return Optional.empty(); } @@ -833,7 +830,7 @@ public class SqlDao extends AbstractDao { track.getIoLock().lock(); } - track.setGroups(this.gson.fromJson(groups.get(), LIST_STRING_TYPE)); + track.setGroups(this.gson.fromJson(groups, LIST_STRING_TYPE)); return Optional.of(track); } finally { @@ -911,91 +908,91 @@ public class SqlDao extends AbstractDao { } @Override - public void saveUUIDData(UUID uuid, String username) throws SQLException { - final String u = username.toLowerCase(); - AtomicReference remoteUserName = new AtomicReference<>(null); + public PlayerSaveResult savePlayerData(UUID uuid, String username) throws SQLException { + username = username.toLowerCase(); - // cleanup any old values - try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_DELETE))) { - ps.setString(1, u); - ps.setString(2, uuid.toString()); - ps.execute(); - } - } + // find any existing mapping + String oldUsername = getPlayerName(uuid); - try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_USERNAME))) { - ps.setString(1, uuid.toString()); - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - remoteUserName.set(rs.getString("username")); + // do the insert + if (!username.equalsIgnoreCase(oldUsername)) { + try (Connection c = this.provider.getConnection()) { + if (oldUsername != null) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE_USERNAME_FOR_UUID))) { + ps.setString(1, username); + ps.setString(2, uuid.toString()); + ps.execute(); + } + } else { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_INSERT))) { + ps.setString(1, uuid.toString()); + ps.setString(2, username); + ps.setString(3, NodeFactory.DEFAULT_GROUP_NAME); + ps.execute(); } } } } - if (remoteUserName.get() != null) { - // the value is already correct - if (remoteUserName.get().equals(u)) { - return; - } + PlayerSaveResult result = PlayerSaveResult.determineBaseResult(username, oldUsername); + Set conflicting = new HashSet<>(); + try (Connection c = this.provider.getConnection()) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) { + ps.setString(1, username); + ps.setString(2, uuid.toString()); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + conflicting.add(UUID.fromString(rs.getString("uuid"))); + } + } + } + } + + if (!conflicting.isEmpty()) { + // remove the mappings for conflicting uuids try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE))) { - ps.setString(1, u); + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) { + ps.setString(1, username); ps.setString(2, uuid.toString()); ps.execute(); } } - } else { - // first time we've seen this uuid - try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_INSERT))) { - ps.setString(1, uuid.toString()); - ps.setString(2, u); - ps.setString(3, NodeFactory.DEFAULT_GROUP_NAME); - ps.execute(); - } - } + result = result.withOtherUuidsPresent(conflicting); } + + return result; } @Override - public UUID getUUID(String username) throws SQLException { - final String u = username.toLowerCase(); - final AtomicReference uuid = new AtomicReference<>(null); - + public UUID getPlayerUuid(String username) throws SQLException { + username = username.toLowerCase(); try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_UUID))) { - ps.setString(1, u); + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_UUID_BY_USERNAME))) { + ps.setString(1, username); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { - uuid.set(UUID.fromString(rs.getString("uuid"))); + return UUID.fromString(rs.getString("uuid")); } } } } - - return uuid.get(); + return null; } @Override - public String getName(UUID uuid) throws SQLException { - final AtomicReference name = new AtomicReference<>(null); - + public String getPlayerName(UUID uuid) throws SQLException { try (Connection c = this.provider.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_USERNAME))) { + try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_USERNAME_BY_UUID))) { ps.setString(1, uuid.toString()); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { - name.set(rs.getString("username")); + return rs.getString("username"); } } } } - - return name.get(); + return null; } /** diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/Uuids.java b/common/src/main/java/me/lucko/luckperms/common/utils/Uuids.java index d70796d1..26ae3965 100644 --- a/common/src/main/java/me/lucko/luckperms/common/utils/Uuids.java +++ b/common/src/main/java/me/lucko/luckperms/common/utils/Uuids.java @@ -41,7 +41,7 @@ public final class Uuids { public static final Predicate PREDICATE = s -> parseNullable(s) != null; @Nullable - private static UUID fromString(String s) { + public static UUID fromString(String s) { try { return UUID.fromString(s); } catch (IllegalArgumentException e) {