Improve the way player uuid data is saved/stored. Add a warning message to catch ip forwarding issues

This commit is contained in:
Luck 2018-03-29 21:46:34 +01:00
parent 8f30176edd
commit e4e93b1af1
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
25 changed files with 485 additions and 223 deletions

View File

@ -254,7 +254,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
public CompletableFuture<Boolean> saveUUIDData(@Nonnull String username, @Nonnull UUID uuid) { public CompletableFuture<Boolean> saveUUIDData(@Nonnull String username, @Nonnull UUID uuid) {
Objects.requireNonNull(username, "username"); Objects.requireNonNull(username, "username");
Objects.requireNonNull(uuid, "uuid"); Objects.requireNonNull(uuid, "uuid");
return this.handle.noBuffer().saveUUIDData(uuid, checkUsername(username)) return this.handle.noBuffer().savePlayerData(uuid, checkUsername(username))
.thenApply(r -> true) .thenApply(r -> true)
.exceptionally(consumeExceptionToFalse()); .exceptionally(consumeExceptionToFalse());
} }
@ -263,13 +263,13 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override @Override
public CompletableFuture<UUID> getUUID(@Nonnull String username) { public CompletableFuture<UUID> getUUID(@Nonnull String username) {
Objects.requireNonNull(username, "username"); Objects.requireNonNull(username, "username");
return this.handle.noBuffer().getUUID(checkUsername(username)); return this.handle.noBuffer().getPlayerUuid(checkUsername(username));
} }
@Nonnull @Nonnull
@Override @Override
public CompletableFuture<String> getName(@Nonnull UUID uuid) { public CompletableFuture<String> getName(@Nonnull UUID uuid) {
Objects.requireNonNull(uuid, "uuid"); Objects.requireNonNull(uuid, "uuid");
return this.handle.noBuffer().getName(uuid); return this.handle.noBuffer().getPlayerName(uuid);
} }
} }

View File

@ -92,7 +92,7 @@ public class GroupListMembers extends SubCommand<Group> {
if (!matchedUsers.isEmpty()) { if (!matchedUsers.isEmpty()) {
LoadingCache<UUID, String> uuidLookups = Caffeine.newBuilder() LoadingCache<UUID, String> uuidLookups = Caffeine.newBuilder()
.build(u -> { .build(u -> {
String s = plugin.getStorage().getName(u).join(); String s = plugin.getStorage().getPlayerName(u).join();
if (s == null || s.isEmpty() || s.equals("null")) { if (s == null || s.isEmpty() || s.equals("null")) {
s = u.toString(); s = u.toString();
} }

View File

@ -86,7 +86,7 @@ public class LogRecent extends SubCommand<Log> {
} }
} }
uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join();
if (uuid == null) { if (uuid == null) {
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) {
Message.USER_NOT_FOUND.send(sender, target); Message.USER_NOT_FOUND.send(sender, target);

View File

@ -81,7 +81,7 @@ public class LogUserHistory extends SubCommand<Log> {
} }
} }
uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join();
if (uuid == null) { if (uuid == null) {
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) {
Message.USER_NOT_FOUND.send(sender, target); Message.USER_NOT_FOUND.send(sender, target);

View File

@ -86,7 +86,7 @@ public class SearchCommand extends SingleCommand {
if (!matchedUsers.isEmpty()) { if (!matchedUsers.isEmpty()) {
LoadingCache<UUID, String> uuidLookups = Caffeine.newBuilder() LoadingCache<UUID, String> uuidLookups = Caffeine.newBuilder()
.build(u -> { .build(u -> {
String s = plugin.getStorage().getName(u).join(); String s = plugin.getStorage().getPlayerName(u).join();
if (s == null || s.isEmpty() || s.equals("null")) { if (s == null || s.isEmpty() || s.equals("null")) {
s = u.toString(); s = u.toString();
} }

View File

@ -73,7 +73,7 @@ public class UserClone extends SubCommand<User> {
} }
} }
uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join();
if (uuid == null) { if (uuid == null) {
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) {
Message.USER_NOT_FOUND.send(sender, target); Message.USER_NOT_FOUND.send(sender, target);

View File

@ -67,6 +67,7 @@ public class UserInfo extends SubCommand<User> {
Message.USER_INFO_GENERAL.send(sender, Message.USER_INFO_GENERAL.send(sender,
user.getName().orElse("Unknown"), user.getName().orElse("Unknown"),
user.getUuid(), user.getUuid(),
user.getUuid().version() == 4 ? "&2mojang" : "&8offline",
status.asString(plugin.getLocaleManager()), status.asString(plugin.getLocaleManager()),
user.getPrimaryGroup().getValue() user.getPrimaryGroup().getValue()
); );

View File

@ -96,7 +96,7 @@ public class UserMainCommand extends MainCommand<User, UserIdentifier> {
} }
} }
uuid = plugin.getStorage().getUUID(target.toLowerCase()).join(); uuid = plugin.getStorage().getPlayerUuid(target.toLowerCase()).join();
if (uuid == null) { if (uuid == null) {
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) {
Message.USER_NOT_FOUND.send(sender, target); Message.USER_NOT_FOUND.send(sender, target);
@ -111,7 +111,7 @@ public class UserMainCommand extends MainCommand<User, UserIdentifier> {
} }
} }
String name = plugin.getStorage().getName(uuid).join(); String name = plugin.getStorage().getPlayerName(uuid).join();
return UserIdentifier.of(uuid, name); return UserIdentifier.of(uuid, name);
} }

View File

@ -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.EventUserLoad;
import me.lucko.luckperms.common.event.impl.EventUserLoginProcess; import me.lucko.luckperms.common.event.impl.EventUserLoginProcess;
import me.lucko.luckperms.common.event.impl.EventUserPromote; import me.lucko.luckperms.common.event.impl.EventUserPromote;
import me.lucko.luckperms.common.event.model.EntitySender; import me.lucko.luckperms.common.event.model.EntitySourceImpl;
import me.lucko.luckperms.common.event.model.SourceEntity; import me.lucko.luckperms.common.event.model.SenderEntity;
import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track; 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) { 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); fireEventAsync(event);
} }
public void handleUserPromote(User user, Track track, String from, String to, Sender source) { 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); fireEventAsync(event);
} }

View File

@ -29,7 +29,7 @@ import me.lucko.luckperms.api.Entity;
import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.event.log.LogNotifyEvent; import me.lucko.luckperms.api.event.log.LogNotifyEvent;
import me.lucko.luckperms.common.event.AbstractEvent; 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 me.lucko.luckperms.common.sender.Sender;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -56,7 +56,7 @@ public class EventLogNotify extends AbstractEvent implements LogNotifyEvent {
@Override @Override
public synchronized Entity getNotifiable() { public synchronized Entity getNotifiable() {
if (this.notifiable == null) { if (this.notifiable == null) {
this.notifiable = new EntitySender(this.sender); this.notifiable = new SenderEntity(this.sender);
} }
return this.notifiable; return this.notifiable;
} }

View File

@ -30,10 +30,10 @@ import me.lucko.luckperms.api.event.source.EntitySource;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
public class SourceEntity implements EntitySource { public class EntitySourceImpl implements EntitySource {
private final Entity entity; private final Entity entity;
public SourceEntity(Entity entity) { public EntitySourceImpl(Entity entity) {
this.entity = entity; this.entity = entity;
} }
@ -51,6 +51,6 @@ public class SourceEntity implements EntitySource {
@Override @Override
public String toString() { public String toString() {
return "Sender(type=ENTITY, entity=" + this.entity + ")"; return "Source(type=ENTITY, entity=" + this.entity + ")";
} }
} }

View File

@ -33,10 +33,10 @@ import java.util.UUID;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class EntitySender implements Entity { public class SenderEntity implements Entity {
private final Sender sender; private final Sender sender;
public EntitySender(Sender sender) { public SenderEntity(Sender sender) {
this.sender = sender; this.sender = sender;
} }
@ -67,6 +67,6 @@ public class EntitySender implements Entity {
@Override @Override
public String toString() { public String toString() {
return "Sender(type=" + getType() + ", sender=" + this.sender + ")"; return "SenderEntity(type=" + getType() + ", sender=" + this.sender + ")";
} }
} }

View File

@ -29,10 +29,10 @@ import me.lucko.luckperms.api.event.source.Source;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
public final class SourceUnknown implements Source { public final class UnknownSource implements Source {
private static final Source INSTANCE = new SourceUnknown(); private static final Source INSTANCE = new UnknownSource();
private SourceUnknown() { private UnknownSource() {
} }

View File

@ -29,6 +29,7 @@ import me.lucko.luckperms.common.assignments.AssignmentRule;
import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.PlayerSaveResult;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -61,11 +62,16 @@ public abstract class AbstractConnectionListener implements ConnectionListener {
this.plugin.getUserManager().getHouseKeeper().registerUsage(u); this.plugin.getUserManager().getHouseKeeper().registerUsage(u);
// save uuid data. // save uuid data.
String name = this.plugin.getStorage().noBuffer().getName(u).join(); PlayerSaveResult saveResult = this.plugin.getStorage().savePlayerData(u, username).join();
if (name == null) { if (saveResult.includes(PlayerSaveResult.Status.CLEAN_INSERT)) {
this.plugin.getEventFactory().handleUserFirstLogin(u, username); 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(); User user = this.plugin.getStorage().noBuffer().loadUser(u, username).join();
if (user == null) { if (user == null) {

View File

@ -309,7 +309,7 @@ public enum Message {
USER_INFO_GENERAL( USER_INFO_GENERAL(
"{PREFIX}&b&l> &bUser Info: &f{}" + "\n" + "{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- &3Status: {}" + "\n" +
"{PREFIX}&f- &3Primary Group: &f{}", "{PREFIX}&f- &3Primary Group: &f{}",
false false

View File

@ -290,17 +290,17 @@ public class AbstractStorage implements Storage {
} }
@Override @Override
public CompletableFuture<Void> saveUUIDData(UUID uuid, String username) { public CompletableFuture<PlayerSaveResult> savePlayerData(UUID uuid, String username) {
return makeFuture(() -> this.dao.saveUUIDData(uuid, username)); return makeFuture(() -> this.dao.savePlayerData(uuid, username));
} }
@Override @Override
public CompletableFuture<UUID> getUUID(String username) { public CompletableFuture<UUID> getPlayerUuid(String username) {
return makeFuture(() -> this.dao.getUUID(username)); return makeFuture(() -> this.dao.getPlayerUuid(username));
} }
@Override @Override
public CompletableFuture<String> getName(UUID uuid) { public CompletableFuture<String> getPlayerName(UUID uuid) {
return makeFuture(() -> this.dao.getName(uuid)); return makeFuture(() -> this.dao.getPlayerName(uuid));
} }
} }

View File

@ -0,0 +1,194 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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> status;
@Nullable private final String oldUsername;
@Nullable private final Set<UUID> otherUuids;
private PlayerSaveResult(EnumSet<Status> status, @Nullable String oldUsername, @Nullable Set<UUID> otherUuids) {
this.status = ImmutableSet.copyOf(status);
this.oldUsername = oldUsername;
this.otherUuids = otherUuids;
}
private PlayerSaveResult(@Nullable String oldUsername, @Nullable Set<UUID> 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<UUID> otherUuids) {
EnumSet<Status> 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<Status> 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<UUID> 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.
*
* <p>This is normal, players are able to change their usernames.</p>
*/
USERNAME_UPDATED,
/**
* There was already a record for the username saved, but it was under a different uuid.
*
* <p>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.</p>
*
* <p>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.</p>
*/
OTHER_UUIDS_PRESENT_FOR_USERNAME,
}
}

View File

@ -99,9 +99,9 @@ public interface Storage {
CompletableFuture<Void> deleteTrack(Track track, DeletionCause cause); CompletableFuture<Void> deleteTrack(Track track, DeletionCause cause);
CompletableFuture<Void> saveUUIDData(UUID uuid, String username); CompletableFuture<PlayerSaveResult> savePlayerData(UUID uuid, String username);
CompletableFuture<UUID> getUUID(String username); CompletableFuture<UUID> getPlayerUuid(String username);
CompletableFuture<String> getName(UUID uuid); CompletableFuture<String> getPlayerName(UUID uuid);
} }

View File

@ -33,6 +33,7 @@ import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.PlayerSaveResult;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -105,12 +106,12 @@ public abstract class AbstractDao {
public abstract void deleteTrack(Track track) throws Exception; 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 @Nullable
public abstract UUID getUUID(String username) throws Exception; public abstract UUID getPlayerUuid(String username) throws Exception;
@Nullable @Nullable
public abstract String getName(UUID uuid) throws Exception; public abstract String getPlayerName(UUID uuid) throws Exception;
} }

View File

@ -35,6 +35,7 @@ import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; 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.SplitStorageType;
import me.lucko.luckperms.common.storage.StorageType; import me.lucko.luckperms.common.storage.StorageType;
@ -191,17 +192,17 @@ public class SplitStorageDao extends AbstractDao {
} }
@Override @Override
public void saveUUIDData(UUID uuid, String username) throws Exception { public PlayerSaveResult savePlayerData(UUID uuid, String username) throws Exception {
this.backing.get(this.types.get(SplitStorageType.UUID)).saveUUIDData(uuid, username); return this.backing.get(this.types.get(SplitStorageType.UUID)).savePlayerData(uuid, username);
} }
@Override @Override
public UUID getUUID(String username) throws Exception { public UUID getPlayerUuid(String username) throws Exception {
return this.backing.get(this.types.get(SplitStorageType.UUID)).getUUID(username); return this.backing.get(this.types.get(SplitStorageType.UUID)).getPlayerUuid(username);
} }
@Override @Override
public String getName(UUID uuid) throws Exception { public String getPlayerName(UUID uuid) throws Exception {
return this.backing.get(this.types.get(SplitStorageType.UUID)).getName(uuid); return this.backing.get(this.types.get(SplitStorageType.UUID)).getPlayerName(uuid);
} }
} }

View File

@ -47,6 +47,7 @@ import me.lucko.luckperms.common.node.NodeHeldPermission;
import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier; 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.AbstractDao;
import me.lucko.luckperms.common.utils.ImmutableCollectors; import me.lucko.luckperms.common.utils.ImmutableCollectors;
import me.lucko.luckperms.common.utils.Uuids; import me.lucko.luckperms.common.utils.Uuids;
@ -670,17 +671,17 @@ public abstract class ConfigurateDao extends AbstractDao {
} }
@Override @Override
public void saveUUIDData(UUID uuid, String username) { public PlayerSaveResult savePlayerData(UUID uuid, String username) {
this.uuidCache.addMapping(uuid, username); return this.uuidCache.addMapping(uuid, username);
} }
@Override @Override
public UUID getUUID(String username) { public UUID getPlayerUuid(String username) {
return this.uuidCache.lookup(username); return this.uuidCache.lookupUuid(username);
} }
@Override @Override
public String getName(UUID uuid) { public String getPlayerName(UUID uuid) {
return this.uuidCache.lookupUsername(uuid); return this.uuidCache.lookupUsername(uuid);
} }

View File

@ -26,9 +26,12 @@
package me.lucko.luckperms.common.storage.dao.file; package me.lucko.luckperms.common.storage.dao.file;
import com.google.common.base.Splitter; 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.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
@ -36,19 +39,60 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class FileUuidCache { public class FileUuidCache {
private static final Splitter KV_SPLIT = Splitter.on('=').omitEmptyStrings(); private static final Splitter KV_SPLIT = Splitter.on(':').omitEmptyStrings();
private static final Splitter TIME_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 // the lookup map
private final Map<String, Map.Entry<UUID, Long>> lookupMap = new ConcurrentHashMap<>(); private final LookupMap lookupMap = new LookupMap();
private static final class LookupMap extends ConcurrentHashMap<UUID, String> {
private final SetMultimap<String, UUID> 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<UUID> lookupUuid(String name) {
return this.reverse.get(name.toLowerCase());
}
}
/** /**
* Adds a mapping to the cache * Adds a mapping to the cache
@ -56,8 +100,25 @@ public class FileUuidCache {
* @param uuid the uuid of the player * @param uuid the uuid of the player
* @param username the username of the player * @param username the username of the player
*/ */
public void addMapping(UUID uuid, String username) { public PlayerSaveResult addMapping(UUID uuid, String username) {
this.lookupMap.put(username.toLowerCase(), Maps.immutableEntry(uuid, DateUtil.unixSecondsNow())); // perform the insert
String oldUsername = this.lookupMap.put(uuid, username);
PlayerSaveResult result = PlayerSaveResult.determineBaseResult(username, oldUsername);
Set<UUID> 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 * @return a uuid, or null
*/ */
@Nullable @Nullable
public UUID lookup(String username) { public UUID lookupUuid(String username) {
Map.Entry<UUID, Long> ret = this.lookupMap.get(username.toLowerCase()); Set<UUID> uuids = this.lookupMap.lookupUuid(username);
return ret == null ? null : ret.getKey(); return Iterables.getFirst(uuids, null);
} }
/** /**
@ -79,23 +140,46 @@ public class FileUuidCache {
* @return a username, or null * @return a username, or null
*/ */
public String lookupUsername(UUID uuid) { public String lookupUsername(UUID uuid) {
String username = null; return this.lookupMap.lookupUsername(uuid);
Long time = Long.MIN_VALUE; }
for (Map.Entry<String, Map.Entry<UUID, Long>> ent : this.lookupMap.entrySet()) { private void loadEntry(String entry) {
if (!ent.getValue().getKey().equals(uuid)) { if (entry.contains(":")) {
continue; // new format
Iterator<String> 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<String> 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<String> 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) { this.lookupMap.put(uuid, usernamePart);
time = t;
username = ent.getKey();
}
} }
return username;
} }
public void load(File file) { public void load(File file) {
@ -104,61 +188,14 @@ public class FileUuidCache {
} }
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
String entry; String entry;
while ((entry = reader.readLine()) != null) { while ((entry = reader.readLine()) != null) {
entry = entry.trim(); entry = entry.trim();
if (entry.isEmpty() || entry.startsWith("#")) { if (entry.isEmpty() || entry.startsWith("#")) {
continue; continue;
} }
loadEntry(entry);
Iterator<String> 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<String> 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));
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -168,13 +205,11 @@ public class FileUuidCache {
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
writer.write("# LuckPerms UUID lookup cache"); writer.write("# LuckPerms UUID lookup cache");
writer.newLine(); writer.newLine();
for (Map.Entry<UUID, String> ent : this.lookupMap.entrySet()) {
for (Map.Entry<String, Map.Entry<UUID, Long>> ent : this.lookupMap.entrySet()) { String out = ent.getKey() + ":" + ent.getValue();
String out = ent.getKey() + "=" + ent.getValue().getKey().toString() + "|" + ent.getValue().getValue().toString();
writer.write(out); writer.write(out);
writer.newLine(); writer.newLine();
} }
writer.flush(); writer.flush();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -34,6 +34,7 @@ import com.mongodb.ServerAddress;
import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.model.UpdateOptions;
import me.lucko.luckperms.api.HeldPermission; 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.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier; 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.StorageCredentials;
import me.lucko.luckperms.common.storage.dao.AbstractDao; import me.lucko.luckperms.common.storage.dao.AbstractDao;
@ -569,29 +571,53 @@ public class MongoDao extends AbstractDao {
} }
@Override @Override
public void saveUUIDData(UUID uuid, String username) { public PlayerSaveResult savePlayerData(UUID uuid, String username) {
username = username.toLowerCase();
MongoCollection<Document> c = this.database.getCollection(this.prefix + "uuid"); MongoCollection<Document> 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<UUID> conflicting = new HashSet<>();
try (MongoCursor<Document> 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 @Override
public UUID getUUID(String username) { public UUID getPlayerUuid(String username) {
MongoCollection<Document> c = this.database.getCollection(this.prefix + "uuid"); MongoCollection<Document> c = this.database.getCollection(this.prefix + "uuid");
try (MongoCursor<Document> cursor = c.find(new Document("name", username.toLowerCase())).iterator()) { Document doc = c.find(new Document("name", username.toLowerCase())).first();
if (cursor.hasNext()) { if (doc != null) {
return cursor.next().get("_id", UUID.class); return doc.get("_id", UUID.class);
}
} }
return null; return null;
} }
@Override @Override
public String getName(UUID uuid) { public String getPlayerName(UUID uuid) {
MongoCollection<Document> c = this.database.getCollection(this.prefix + "uuid"); MongoCollection<Document> c = this.database.getCollection(this.prefix + "uuid");
try (MongoCursor<Document> cursor = c.find(new Document("_id", uuid)).iterator()) { Document doc = c.find(new Document("_id", uuid)).first();
if (cursor.hasNext()) { if (doc != null) {
return cursor.next().get("name", String.class); return doc.get("name", String.class);
}
} }
return null; return null;
} }

View File

@ -46,6 +46,7 @@ import me.lucko.luckperms.common.node.NodeHeldPermission;
import me.lucko.luckperms.common.node.NodeModel; import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier; 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.AbstractDao;
import me.lucko.luckperms.common.storage.dao.sql.connection.AbstractConnectionFactory; import me.lucko.luckperms.common.storage.dao.sql.connection.AbstractConnectionFactory;
import me.lucko.luckperms.common.storage.dao.sql.connection.file.SQLiteConnectionFactory; 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.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; 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_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 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_BY_USERNAME = "SELECT uuid FROM {prefix}players WHERE username=? LIMIT 1";
private static final String PLAYER_SELECT_UUID = "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_SELECT_USERNAME = "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_SELECT_PRIMARY_GROUP = "SELECT primary_group FROM {prefix}players WHERE uuid=? LIMIT 1";
private static final String PLAYER_INSERT = "INSERT INTO {prefix}players VALUES(?, ?, ?)"; 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_SELECT_ALL_UUIDS_BY_USERNAME = "SELECT uuid FROM {prefix}players WHERE username=? AND NOT uuid=?";
private static final String PLAYER_DELETE = "DELETE 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_UPDATE_PRIMARY_GROUP = "UPDATE {prefix}players SET primary_group=? WHERE 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_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=?"; 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(); user.getIoLock().lock();
try { try {
List<NodeModel> data = new ArrayList<>(); List<NodeModel> data = new ArrayList<>();
AtomicReference<String> primaryGroup = new AtomicReference<>(null); String primaryGroup = null;
AtomicReference<String> userName = new AtomicReference<>(null); String userName = null;
// Collect user permissions // Collect user permissions
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
@ -319,27 +319,26 @@ public class SqlDao extends AbstractDao {
// Collect user meta (username & primary group) // Collect user meta (username & primary group)
try (Connection c = this.provider.getConnection()) { 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()); ps.setString(1, user.getUuid().toString());
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { if (rs.next()) {
userName.set(rs.getString("username")); userName = rs.getString("username");
primaryGroup.set(rs.getString("primary_group")); primaryGroup = rs.getString("primary_group");
} }
} }
} }
} }
// update username & primary group // update username & primary group
String pg = primaryGroup.get(); if (primaryGroup == null) {
if (pg == null) { primaryGroup = NodeFactory.DEFAULT_GROUP_NAME;
pg = 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 // 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 the user has any data in storage
if (!data.isEmpty()) { if (!data.isEmpty()) {
@ -378,7 +377,7 @@ public class SqlDao extends AbstractDao {
ps.setString(1, user.getUuid().toString()); ps.setString(1, user.getUuid().toString());
ps.execute(); 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(1, NodeFactory.DEFAULT_GROUP_NAME);
ps.setString(2, user.getUuid().toString()); ps.setString(2, user.getUuid().toString());
ps.execute(); ps.execute();
@ -453,7 +452,7 @@ public class SqlDao extends AbstractDao {
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
boolean hasPrimaryGroupSaved; 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()); ps.setString(1, user.getUuid().toString());
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
hasPrimaryGroupSaved = rs.next(); hasPrimaryGroupSaved = rs.next();
@ -462,7 +461,7 @@ public class SqlDao extends AbstractDao {
if (hasPrimaryGroupSaved) { if (hasPrimaryGroupSaved) {
// update // 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(1, user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME));
ps.setString(2, user.getUuid().toString()); ps.setString(2, user.getUuid().toString());
ps.execute(); ps.execute();
@ -772,24 +771,23 @@ public class SqlDao extends AbstractDao {
Track track = this.plugin.getTrackManager().getOrMake(name); Track track = this.plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock(); track.getIoLock().lock();
try { try {
AtomicBoolean exists = new AtomicBoolean(false); boolean exists = false;
AtomicReference<String> groups = new AtomicReference<>(null); String groups = null;
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(TRACK_SELECT))) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(TRACK_SELECT))) {
ps.setString(1, track.getName()); ps.setString(1, track.getName());
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { if (rs.next()) {
exists.set(true); exists = true;
groups.set(rs.getString("groups")); groups = rs.getString("groups");
} }
} }
} }
} }
if (exists.get()) { if (exists) {
// Track exists, let's load. // 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 { } else {
String json = this.gson.toJson(track.getGroups()); String json = this.gson.toJson(track.getGroups());
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
@ -813,14 +811,13 @@ public class SqlDao extends AbstractDao {
track.getIoLock().lock(); track.getIoLock().lock();
} }
try { try {
AtomicReference<String> groups = new AtomicReference<>(null); String groups;
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(TRACK_SELECT))) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(TRACK_SELECT))) {
ps.setString(1, name); ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { if (rs.next()) {
groups.set(rs.getString("groups")); groups = rs.getString("groups");
} else { } else {
return Optional.empty(); return Optional.empty();
} }
@ -833,7 +830,7 @@ public class SqlDao extends AbstractDao {
track.getIoLock().lock(); 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); return Optional.of(track);
} finally { } finally {
@ -911,91 +908,91 @@ public class SqlDao extends AbstractDao {
} }
@Override @Override
public void saveUUIDData(UUID uuid, String username) throws SQLException { public PlayerSaveResult savePlayerData(UUID uuid, String username) throws SQLException {
final String u = username.toLowerCase(); username = username.toLowerCase();
AtomicReference<String> remoteUserName = new AtomicReference<>(null);
// cleanup any old values // find any existing mapping
try (Connection c = this.provider.getConnection()) { String oldUsername = getPlayerName(uuid);
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_DELETE))) {
ps.setString(1, u);
ps.setString(2, uuid.toString());
ps.execute();
}
}
try (Connection c = this.provider.getConnection()) { // do the insert
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_USERNAME))) { if (!username.equalsIgnoreCase(oldUsername)) {
ps.setString(1, uuid.toString()); try (Connection c = this.provider.getConnection()) {
try (ResultSet rs = ps.executeQuery()) { if (oldUsername != null) {
if (rs.next()) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
remoteUserName.set(rs.getString("username")); 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) { PlayerSaveResult result = PlayerSaveResult.determineBaseResult(username, oldUsername);
// the value is already correct
if (remoteUserName.get().equals(u)) {
return;
}
Set<UUID> 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 (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_UPDATE))) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, u); ps.setString(1, username);
ps.setString(2, uuid.toString()); ps.setString(2, uuid.toString());
ps.execute(); ps.execute();
} }
} }
} else { result = result.withOtherUuidsPresent(conflicting);
// 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();
}
}
} }
return result;
} }
@Override @Override
public UUID getUUID(String username) throws SQLException { public UUID getPlayerUuid(String username) throws SQLException {
final String u = username.toLowerCase(); username = username.toLowerCase();
final AtomicReference<UUID> uuid = new AtomicReference<>(null);
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_UUID))) { try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(PLAYER_SELECT_UUID_BY_USERNAME))) {
ps.setString(1, u); ps.setString(1, username);
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { if (rs.next()) {
uuid.set(UUID.fromString(rs.getString("uuid"))); return UUID.fromString(rs.getString("uuid"));
} }
} }
} }
} }
return null;
return uuid.get();
} }
@Override @Override
public String getName(UUID uuid) throws SQLException { public String getPlayerName(UUID uuid) throws SQLException {
final AtomicReference<String> name = new AtomicReference<>(null);
try (Connection c = this.provider.getConnection()) { 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()); ps.setString(1, uuid.toString());
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { if (rs.next()) {
name.set(rs.getString("username")); return rs.getString("username");
} }
} }
} }
} }
return null;
return name.get();
} }
/** /**

View File

@ -41,7 +41,7 @@ public final class Uuids {
public static final Predicate<String> PREDICATE = s -> parseNullable(s) != null; public static final Predicate<String> PREDICATE = s -> parseNullable(s) != null;
@Nullable @Nullable
private static UUID fromString(String s) { public static UUID fromString(String s) {
try { try {
return UUID.fromString(s); return UUID.fromString(s);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {