Fix another bug where usernames can be saved as null for FlatFileDB

This commit is contained in:
nossr50 2021-04-15 14:43:37 -07:00
parent 1269652e94
commit 48de5057a4
19 changed files with 686 additions and 178 deletions

View File

@ -1,3 +1,19 @@
Version 2.1.192
Removed some debug messages from FlatFileDatabaseManager
Fixed another bug where player names could be saved as null for FlatFileDB (they will update on the players next login at the next save interval)
(API) Removed deprecation from com.gmail.nossr50.api.ExperienceAPI.getOfflineProfile(java.lang.String)
(API) Added com.gmail.nossr50.api.DatabaseAPI.doesPlayerExistInDB(org.bukkit.OfflinePlayer)
(API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineProfile(org.bukkit.OfflinePlayer)
(API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXP(org.bukkit.OfflinePlayer, java.lang.String)
(API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXPRaw(org.bukkit.OfflinePlayer, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
(API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXPRaw(org.bukkit.OfflinePlayer, java.lang.String)
(API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXPToNextLevel(org.bukkit.OfflinePlayer, java.lang.String)
(API) Added com.gmail.nossr50.api.DatabaseAPI.doesPlayerExistInDB(java.lang.String)
(Unit Tests) Added some more unit tests to FlatFileDB
NOTES:
I removed a lot of API that should honestly never have been added, this will break some plugins, those plugins will have to update.
Version 2.1.191
Fixed a bug related to our blocktracker
Fixed a bug that prevented the leaderboards from working on FlatFile in some circumstances

View File

@ -2,6 +2,8 @@ package com.gmail.nossr50.api;
import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.mcMMO;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@ -9,20 +11,38 @@ public class DatabaseAPI {
/**
* Checks if a player exists in the mcMMO Database
* @param uuid player UUID
* @param offlinePlayer target player
* @return true if the player exists in the DB, false if they do not
*/
public boolean doesPlayerExistInDB(String uuid) {
return doesPlayerExistInDB(UUID.fromString(uuid));
public boolean doesPlayerExistInDB(@NotNull OfflinePlayer offlinePlayer) {
PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(offlinePlayer);
return playerProfile.isLoaded();
}
/**
* Checks if a player exists in the mcMMO Database
* @param uuid player UUID
* @param uuid target player
* @return true if the player exists in the DB, false if they do not
*/
public boolean doesPlayerExistInDB(UUID uuid) {
PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, null);
public boolean doesPlayerExistInDB(@NotNull UUID uuid) {
PlayerProfile playerProfile = null;
try {
playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
} catch (Exception e) {
return false;
}
return playerProfile.isLoaded();
}
/**
* Checks if a player exists in the mcMMO Database
* @param playerName target player
* @return true if the player exists in the DB, false if they do not
*/
public boolean doesPlayerExistInDB(@NotNull String playerName) {
PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName);
return playerProfile.isLoaded();
}

View File

@ -13,11 +13,11 @@ import com.gmail.nossr50.skills.child.FamilyTree;
import com.gmail.nossr50.util.player.UserManager;
import com.gmail.nossr50.util.skills.CombatUtils;
import com.gmail.nossr50.util.skills.SkillTools;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.block.BlockState;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Set;
@ -432,6 +432,23 @@ public final class ExperienceAPI {
return getOfflineProfile(uuid).getSkillXpLevel(getNonChildSkillType(skillType));
}
/**
* Get the amount of XP an offline player has in a specific skill.
* </br>
* This function is designed for API usage.
*
* @param offlinePlayer The player to get XP for
* @param skillType The skill to get XP for
* @return the amount of XP in a given skill
*
* @throws InvalidSkillException if the given skill is not valid
* @throws InvalidPlayerException if the given player does not exist in the database
* @throws UnsupportedOperationException if the given skill is a child skill
*/
public static int getOfflineXP(@NotNull OfflinePlayer offlinePlayer, @NotNull String skillType) throws InvalidPlayerException {
return getOfflineProfile(offlinePlayer).getSkillXpLevel(getNonChildSkillType(skillType));
}
/**
* Get the raw amount of XP a player has in a specific skill.
* </br>
@ -483,6 +500,30 @@ public final class ExperienceAPI {
return getOfflineProfile(uuid).getSkillXpLevelRaw(getNonChildSkillType(skillType));
}
/**
* Get the raw amount of XP an offline player has in a specific skill.
* </br>
* This function is designed for API usage.
*
* @param offlinePlayer The player to get XP for
* @param skillType The skill to get XP for
* @return the amount of XP in a given skill
*
* @throws InvalidSkillException if the given skill is not valid
* @throws InvalidPlayerException if the given player does not exist in the database
* @throws UnsupportedOperationException if the given skill is a child skill
*/
public static float getOfflineXPRaw(@NotNull OfflinePlayer offlinePlayer, @NotNull String skillType) throws InvalidPlayerException, UnsupportedOperationException, InvalidSkillException {
return getOfflineProfile(offlinePlayer).getSkillXpLevelRaw(getNonChildSkillType(skillType));
}
public static float getOfflineXPRaw(@NotNull OfflinePlayer offlinePlayer, @NotNull PrimarySkillType skillType) throws InvalidPlayerException, UnsupportedOperationException {
if(SkillTools.isChildSkill(skillType))
throw new UnsupportedOperationException();
return getOfflineProfile(offlinePlayer).getSkillXpLevelRaw(skillType);
}
/**
* Get the total amount of XP needed to reach the next level.
* </br>
@ -530,10 +571,27 @@ public final class ExperienceAPI {
* @throws InvalidPlayerException if the given player does not exist in the database
* @throws UnsupportedOperationException if the given skill is a child skill
*/
public static int getOfflineXPToNextLevel(UUID uuid, String skillType) {
public static int getOfflineXPToNextLevel(@NotNull UUID uuid, @NotNull String skillType) {
return getOfflineProfile(uuid).getXpToLevel(getNonChildSkillType(skillType));
}
/**
* Get the total amount of XP an offline player needs to reach the next level.
* </br>
* This function is designed for API usage.
*
* @param offlinePlayer The player to get XP for
* @param skillType The skill to get XP for
* @return the total amount of XP needed to reach the next level
*
* @throws InvalidSkillException if the given skill is not valid
* @throws InvalidPlayerException if the given player does not exist in the database
* @throws UnsupportedOperationException if the given skill is a child skill
*/
public static int getOfflineXPToNextLevel(@NotNull OfflinePlayer offlinePlayer, @NotNull String skillType) throws UnsupportedOperationException, InvalidSkillException, InvalidPlayerException {
return getOfflineProfile(offlinePlayer).getXpToLevel(getNonChildSkillType(skillType));
}
/**
* Get the amount of XP remaining until the next level.
* </br>
@ -595,6 +653,26 @@ public final class ExperienceAPI {
return profile.getXpToLevel(skill) - profile.getSkillXpLevelRaw(skill);
}
/**
* Get the amount of XP an offline player has left before leveling up.
* </br>
* This function is designed for API usage.
*
* @param offlinePlayer The player to get XP for
* @param skillType The skill to get XP for
* @return the amount of XP needed to reach the next level
*
* @throws InvalidSkillException if the given skill is not valid
* @throws InvalidPlayerException if the given player does not exist in the database
* @throws UnsupportedOperationException if the given skill is a child skill
*/
public static float getOfflineXPRemaining(OfflinePlayer offlinePlayer, String skillType) throws InvalidSkillException, InvalidPlayerException, UnsupportedOperationException {
PrimarySkillType skill = getNonChildSkillType(skillType);
PlayerProfile profile = getOfflineProfile(offlinePlayer);
return profile.getXpToLevel(skill) - profile.getSkillXpLevelRaw(skill);
}
/**
* Add levels to a skill.
* </br>
@ -1129,25 +1207,22 @@ public final class ExperienceAPI {
}
// Utility methods follow.
private static void addOfflineXP(UUID playerUniqueId, PrimarySkillType skill, int XP) {
private static void addOfflineXP(@NotNull UUID playerUniqueId, @NotNull PrimarySkillType skill, int XP) {
PlayerProfile profile = getOfflineProfile(playerUniqueId);
profile.addXp(skill, XP);
profile.save(true);
}
@Deprecated
private static void addOfflineXP(String playerName, PrimarySkillType skill, int XP) {
private static void addOfflineXP(@NotNull String playerName, @NotNull PrimarySkillType skill, int XP) {
PlayerProfile profile = getOfflineProfile(playerName);
profile.addXp(skill, XP);
profile.scheduleAsyncSave();
}
private static PlayerProfile getOfflineProfile(UUID uuid) throws InvalidPlayerException {
OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(uuid);
String playerName = offlinePlayer.getName();
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, playerName);
private static @NotNull PlayerProfile getOfflineProfile(@NotNull UUID uuid) throws InvalidPlayerException {
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
if (!profile.isLoaded()) {
throw new InvalidPlayerException();
@ -1156,11 +1231,18 @@ public final class ExperienceAPI {
return profile;
}
@Deprecated
private static PlayerProfile getOfflineProfile(String playerName) throws InvalidPlayerException {
OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(playerName);
UUID uuid = offlinePlayer.getUniqueId();
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, playerName);
private static @NotNull PlayerProfile getOfflineProfile(@NotNull OfflinePlayer offlinePlayer) throws InvalidPlayerException {
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(offlinePlayer);
if (!profile.isLoaded()) {
throw new InvalidPlayerException();
}
return profile;
}
private static @NotNull PlayerProfile getOfflineProfile(@NotNull String playerName) throws InvalidPlayerException {
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName);
if (!profile.isLoaded()) {
throw new InvalidPlayerException();

View File

@ -58,7 +58,7 @@ public class ConvertDatabaseCommand implements CommandExecutor {
UserManager.clearAll();
for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId(), null);
PlayerProfile profile = oldDatabase.loadPlayerProfile(player);
if (profile.isLoaded()) {
mcMMO.getDatabaseManager().saveUser(profile);

View File

@ -97,12 +97,9 @@ public abstract class ExperienceCommand implements TabExecutor {
// If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
if (mcMMOPlayer == null) {
UUID uuid = null;
OfflinePlayer offlinePlayer = mcMMO.p.getServer().getOfflinePlayer(playerName);
PlayerProfile profile;
uuid = offlinePlayer.getUniqueId();
profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, null);
profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName);
//Check loading by UUID
if (CommandUtils.unloadedProfile(sender, profile)) {

View File

@ -79,11 +79,8 @@ public class SkillresetCommand implements TabExecutor {
// If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
if (mcMMOPlayer == null) {
UUID uuid = null;
OfflinePlayer player = mcMMO.p.getServer().getOfflinePlayer(playerName);
uuid = player.getUniqueId();
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, playerName);
OfflinePlayer offlinePlayer = mcMMO.p.getServer().getOfflinePlayer(playerName);
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(offlinePlayer);
//Check loading by UUID
if (CommandUtils.unloadedProfile(sender, profile)) {

View File

@ -1,5 +1,6 @@
package com.gmail.nossr50.database;
import com.gmail.nossr50.api.exceptions.InvalidPlayerException;
import com.gmail.nossr50.api.exceptions.InvalidSkillException;
import com.gmail.nossr50.datatypes.database.DatabaseType;
import com.gmail.nossr50.datatypes.database.PlayerStat;
@ -93,18 +94,7 @@ public interface DatabaseManager {
*/
@NotNull PlayerProfile loadPlayerProfile(@NotNull String playerName);
default @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer) {
return loadPlayerProfile(offlinePlayer.getUniqueId(), offlinePlayer.getName());
}
/**
* Load a player from the database.
* @param uuid The uuid of the player to load from the database
* @return The player's data, or an unloaded PlayerProfile if not found
* @deprecated Use {@link DatabaseManager#loadPlayerProfile(org.bukkit.OfflinePlayer)} or {@link DatabaseManager#loadPlayerProfile(java.util.UUID)} if possible
*/
@Deprecated
@NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName);
@NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer);
@NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid);

View File

@ -17,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.security.InvalidParameterException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@ -341,14 +342,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
String line;
boolean wroteUser = false;
if(testing) {
System.out.println("-- saveUser bufferedreader feed --");
}
// While not at the end of the file
while ((line = in.readLine()) != null) {
if(testing) {
System.out.println(line);
}
if(line.startsWith("#")) {
writer.append(line).append("\r\n");
continue;
@ -384,7 +379,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
writer.append(line).append("\r\n"); //Not the user so write it to file and move on
} else {
//User found
writeUserToLine(profile, playerName, uuid, writer);
writeUserToLine(profile, writer);
wroteUser = true;
}
}
@ -393,12 +388,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
* If we couldn't find the user in the DB we need to add him
*/
if(!wroteUser) {
writeUserToLine(profile, playerName, uuid, writer);
}
if(testing) {
System.out.println("-- saveUser (FileWriter contents before save) --");
System.out.println(writer.toString());
writeUserToLine(profile, writer);
}
// Write the new file
@ -431,8 +421,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
}
}
public void writeUserToLine(@NotNull PlayerProfile profile, @NotNull String playerName, @Nullable UUID uuid, @NotNull Appendable appendable) throws IOException {
appendable.append(playerName).append(":");
public void writeUserToLine(@NotNull PlayerProfile profile, @NotNull Appendable appendable) throws IOException {
appendable.append(profile.getPlayerName()).append(":");
appendable.append(String.valueOf(profile.getSkillLevel(PrimarySkillType.MINING))).append(":");
appendable.append(IGNORED).append(":");
appendable.append(IGNORED).append(":");
@ -473,7 +463,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
appendable.append(IGNORED).append(":"); //mob health bar
appendable.append(String.valueOf(profile.getSkillLevel(PrimarySkillType.ALCHEMY))).append(":");
appendable.append(String.valueOf(profile.getSkillXpLevel(PrimarySkillType.ALCHEMY))).append(":");
appendable.append(uuid != null ? uuid.toString() : "NULL").append(":");
appendable.append(profile.getUniqueId() != null ? profile.getUniqueId().toString() : "NULL").append(":");
appendable.append(String.valueOf(profile.getScoreboardTipsShown())).append(":");
appendable.append(String.valueOf(profile.getUniqueData(UniqueDataType.CHIMAERA_WING_DATS))).append(":");
appendable.append(String.valueOf(profile.getLastLogin())).append(":"); //overhaul last login
@ -527,7 +517,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
}
try (FileWriter fileWriter = new FileWriter(usersFile)) {
writeUserToLine(playerProfile, playerName, uuid, stringBuilder);
writeUserToLine(playerProfile, stringBuilder);
fileWriter.write(stringBuilder.toString());
} catch (Exception e) {
e.printStackTrace();
@ -541,103 +531,53 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
}
public @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer) {
return loadPlayerByUUID(offlinePlayer.getUniqueId(), offlinePlayer.getName(), offlinePlayer.isOnline());
return processUserQuery(getUserQuery(offlinePlayer.getUniqueId(), offlinePlayer.getName()));
}
public @NotNull PlayerProfile loadPlayerProfile(@NotNull String playerName) {
return loadPlayerByName(playerName);
}
public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) {
return loadPlayerByUUID(uuid, playerName, false);
return processUserQuery(getUserQuery(null, playerName));
}
public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid) {
return loadPlayerByUUID(uuid, null, false);
return processUserQuery(getUserQuery(uuid, null));
}
private @NotNull UserQuery getUserQuery(@Nullable UUID uuid, @Nullable String playerName) throws NullPointerException {
boolean hasName = playerName != null && !playerName.equalsIgnoreCase("null");
if(hasName && uuid != null) {
return new UserQueryFull(playerName, uuid);
} else if (uuid != null) {
return new UserQueryUUIDImpl(uuid);
} else if(hasName) {
return new UserQueryNameImpl(playerName);
} else {
throw new NullPointerException("Both name and UUID cannot be null, at least one must be non-null!");
}
}
/**
* Find and load a player by UUID
* Find and load a player by UUID/Name
* If the name isn't null and doesn't match the name in the DB, the players name is then replaced/updated
*
* @param uuid target uuid
* @param playerName target player name
* @param replaceName name to replace if the found name differs
* @param userQuery the query
* @return a profile with the targets data or an unloaded profile if no data was found
* @deprecated only use this if you know what you are doing, replacing the name can cause havoc
*/
@Deprecated
public @NotNull PlayerProfile loadPlayerByUUID(@NotNull UUID uuid, @Nullable String playerName, boolean replaceName) {
BufferedReader in = null;
synchronized (fileWritingLock) {
try {
// Open the user file
in = new BufferedReader(new FileReader(usersFilePath));
String line;
while ((line = in.readLine()) != null) {
// Find if the line contains the player we want.
String[] rawSplitData = line.split(":");
/* Don't read corrupt data */
if(rawSplitData.length < (UUID_INDEX + 1)) {
continue;
}
/* Does this entry have a UUID? */
if (rawSplitData[UUID_INDEX].equalsIgnoreCase("NULL")
|| rawSplitData[UUID_INDEX].isEmpty()
|| rawSplitData[UUID_INDEX].equalsIgnoreCase("")) {
continue; //No UUID entry found for this data in the DB, go to next entry
}
// Compare provided UUID to DB
if (!rawSplitData[UUID_INDEX].equalsIgnoreCase(uuid.toString())) {
continue; //Doesn't match, go to the next entry
}
/*
* UUID Matched!
* Making it this far means the current data line is considered a match
*/
/* Check for nickname changes and update since we are here anyways */
if(playerName != null) {
if(replaceName) {
logger.info("A users name is being updated, this can happen from either a call to our API or they simply changed their name");
if (!rawSplitData[USERNAME_INDEX].equalsIgnoreCase(playerName)) {
//logger.info("Name updated for player: " + rawSplitData[USERNAME_INDEX] + " => " + playerName);
rawSplitData[USERNAME_INDEX] = playerName;
}
}
}
return loadFromLine(rawSplitData);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// I have no idea why it's necessary to inline tryClose() here, but it removes
// a resource leak warning, and I'm trusting the compiler on this one.
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Ignore
}
}
}
private @NotNull PlayerProfile processUserQuery(@NotNull UserQuery userQuery) throws RuntimeException {
switch(userQuery.getType()) {
case UUID_AND_NAME:
return queryByUUIDAndName((UserQueryFull) userQuery);
case UUID:
return queryByUUID((UserQueryUUID) userQuery);
case NAME:
return queryByName((UserQueryNameImpl) userQuery);
default:
throw new RuntimeException("No case for this UserQueryType!");
}
/*
* No match was found in the file
*/
return grabUnloadedProfile(uuid, playerName); //Create an empty new profile and return
}
private @NotNull PlayerProfile loadPlayerByName(@NotNull String playerName) {
private @NotNull PlayerProfile queryByName(@NotNull UserQueryName userQuery) {
String playerName = userQuery.getName();
BufferedReader in = null;
synchronized (fileWritingLock) {
@ -646,19 +586,23 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
in = new BufferedReader(new FileReader(usersFilePath));
String line;
while ((line = in.readLine()) != null) {
if(line.startsWith("#")) {
continue;
}
// Find if the line contains the player we want.
String[] rawSplitData = line.split(":");
/* Don't read corrupt data */
if(rawSplitData.length < (USERNAME_INDEX + 1)) {
continue;
}
//If we couldn't find anyone
if(playerName.equalsIgnoreCase(rawSplitData[USERNAME_INDEX])) {
return loadFromLine(rawSplitData);
@ -679,10 +623,135 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
}
}
//Return a new blank profile
return new PlayerProfile(playerName, new UUID(0, 0), startingLevel);
}
private @NotNull PlayerProfile queryByUUID(@NotNull UserQueryUUID userQuery) {
BufferedReader in = null;
UUID uuid = userQuery.getUUID();
synchronized (fileWritingLock) {
try {
// Open the user file
in = new BufferedReader(new FileReader(usersFilePath));
String line;
while ((line = in.readLine()) != null) {
if(line.startsWith("#")) {
continue;
}
// Find if the line contains the player we want.
String[] rawSplitData = line.split(":");
/* Don't read corrupt data */
if(rawSplitData.length < (UUID_INDEX + 1)) {
continue;
}
try {
UUID fromDataUUID = UUID.fromString(rawSplitData[UUID_INDEX]);
if(fromDataUUID.equals(uuid)) {
return loadFromLine(rawSplitData);
}
} catch (Exception e) {
if(testing) {
e.printStackTrace();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// I have no idea why it's necessary to inline tryClose() here, but it removes
// a resource leak warning, and I'm trusting the compiler on this one.
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Ignore
}
}
}
}
/*
* No match was found in the file
*/
return grabUnloadedProfile(uuid, "Player-Not-Found="+uuid.toString());
}
private @NotNull PlayerProfile queryByUUIDAndName(@NotNull UserQueryFull userQuery) {
BufferedReader in = null;
String playerName = userQuery.getName();
UUID uuid = userQuery.getUUID();
synchronized (fileWritingLock) {
try {
// Open the user file
in = new BufferedReader(new FileReader(usersFilePath));
String line;
while ((line = in.readLine()) != null) {
if(line.startsWith("#")) {
continue;
}
// Find if the line contains the player we want.
String[] rawSplitData = line.split(":");
/* Don't read corrupt data */
if(rawSplitData.length < (UUID_INDEX + 1)) {
continue;
}
try {
UUID fromDataUUID = UUID.fromString(rawSplitData[UUID_INDEX]);
if(fromDataUUID.equals(uuid)) {
//Matched UUID, now check if name matches
String dbPlayerName = rawSplitData[USERNAME_INDEX];
boolean matchingName = dbPlayerName.equalsIgnoreCase(playerName);
if (!matchingName) {
logger.info("When loading user: "+playerName +" with UUID of (" + uuid.toString()
+") we found a mismatched name, the name in the DB will be replaced (DB name: "+dbPlayerName+")");
//logger.info("Name updated for player: " + rawSplitData[USERNAME_INDEX] + " => " + playerName);
rawSplitData[USERNAME_INDEX] = playerName;
}
//TODO: Logic to replace name here
return loadFromLine(rawSplitData);
}
} catch (Exception e) {
if(testing) {
e.printStackTrace();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// I have no idea why it's necessary to inline tryClose() here, but it removes
// a resource leak warning, and I'm trusting the compiler on this one.
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Ignore
}
}
}
}
/*
* No match was found in the file
*/
return grabUnloadedProfile(uuid, playerName); //Create an empty new profile and return
}
private @NotNull PlayerProfile grabUnloadedProfile(@NotNull UUID uuid, @Nullable String playerName) {
if(playerName == null) {
playerName = ""; //No name for you boy!

View File

@ -15,6 +15,7 @@ import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.skills.SkillTools;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
@ -515,7 +516,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
if (id == -1) {
return new PlayerProfile(player.getName(), player.getUniqueId(), false, mcMMO.p.getAdvancedConfig().getStartingLevel());
} else {
return loadPlayerProfile(player.getUniqueId(), player.getName());
return loadPlayerProfile(player);
}
} catch (SQLException e) {
e.printStackTrace();
@ -571,7 +572,12 @@ public final class SQLDatabaseManager implements DatabaseManager {
}
}
public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) {
@Override
public @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer) {
return loadPlayerFromDB(offlinePlayer.getUniqueId(), offlinePlayer.getName());
}
public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) {
return loadPlayerFromDB(uuid, playerName);
}

View File

@ -0,0 +1,7 @@
package com.gmail.nossr50.database;
import org.jetbrains.annotations.NotNull;
public interface UserQuery {
@NotNull UserQueryType getType();
}

View File

@ -0,0 +1,31 @@
package com.gmail.nossr50.database;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class UserQueryFull implements UserQueryUUID, UserQueryName {
private final @NotNull String name;
private final @NotNull UUID uuid;
public UserQueryFull(@NotNull String name, @NotNull UUID uuid) {
this.name = name;
this.uuid = uuid;
}
@Override
public @NotNull UserQueryType getType() {
return UserQueryType.UUID_AND_NAME;
}
@Override
public @NotNull String getName() {
return name;
}
@Override
public @NotNull UUID getUUID() {
return uuid;
}
}

View File

@ -0,0 +1,7 @@
package com.gmail.nossr50.database;
import org.jetbrains.annotations.NotNull;
public interface UserQueryName extends UserQuery {
@NotNull String getName();
}

View File

@ -0,0 +1,20 @@
package com.gmail.nossr50.database;
import org.jetbrains.annotations.NotNull;
public class UserQueryNameImpl implements UserQueryName {
private final @NotNull String name;
public UserQueryNameImpl(@NotNull String name) {
this.name = name;
}
@Override
public @NotNull UserQueryType getType() {
return UserQueryType.NAME;
}
public @NotNull String getName() {
return name;
}
}

View File

@ -0,0 +1,7 @@
package com.gmail.nossr50.database;
public enum UserQueryType {
UUID_AND_NAME,
UUID,
NAME
}

View File

@ -0,0 +1,11 @@
package com.gmail.nossr50.database;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public interface UserQueryUUID extends UserQuery {
@NotNull UUID getUUID();
}

View File

@ -0,0 +1,23 @@
package com.gmail.nossr50.database;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class UserQueryUUIDImpl implements UserQueryUUID {
private final @NotNull UUID uuid;
public UserQueryUUIDImpl(@NotNull UUID uuid) {
this.uuid = uuid;
}
@Override
public @NotNull UserQueryType getType() {
return UserQueryType.UUID;
}
@Override
public @NotNull UUID getUUID() {
return uuid;
}
}

View File

@ -22,7 +22,7 @@ import java.util.concurrent.DelayQueue;
public class PlayerProfile {
private final String playerName;
private UUID uuid;
private @Nullable UUID uuid;
private boolean loaded;
private volatile boolean changed;
@ -49,7 +49,7 @@ public class PlayerProfile {
}
//TODO: Add deprecated constructor w/o startinglevel
public PlayerProfile(String playerName, UUID uuid, int startingLevel) {
public PlayerProfile(String playerName, @Nullable UUID uuid, int startingLevel) {
this.uuid = uuid;
this.playerName = playerName;
@ -100,10 +100,6 @@ public class PlayerProfile {
new PlayerProfileSaveTask(this, false).runTaskAsynchronously(mcMMO.p);
}
public void scheduleSyncSave() {
new PlayerProfileSaveTask(this, true).runTask(mcMMO.p);
}
public void scheduleAsyncSaveDelay() {
new PlayerProfileSaveTask(this, false).runTaskLaterAsynchronously(mcMMO.p, 20);
}
@ -126,8 +122,7 @@ public class PlayerProfile {
if (changed) {
mcMMO.p.getLogger().severe("PlayerProfile saving failed for player: " + playerName + " " + uuid);
if(saveAttempts > 0)
{
if(saveAttempts > 0) {
mcMMO.p.getLogger().severe("Attempted to save profile for player "+getPlayerName()
+ " resulted in failure. "+saveAttempts+" have been made so far.");
}
@ -138,7 +133,7 @@ public class PlayerProfile {
//Back out of async saving if we detect a server shutdown, this is not always going to be caught
if(mcMMO.isServerShutdownExecuted() || useSync)
scheduleSyncSave(); //Execute sync saves immediately
new PlayerProfileSaveTask(this, true).runTask(mcMMO.p);
else
scheduleAsyncSave();

View File

@ -41,7 +41,7 @@ public class PlayerProfileLoadingTask extends BukkitRunnable {
return;
}
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player.getUniqueId(), player.getName());
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player);
if(!profile.isLoaded()) {
mcMMO.p.getLogger().info("Creating new data for player: "+player.getName());

View File

@ -9,18 +9,25 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
import com.gmail.nossr50.util.skills.SkillTools;
import com.google.common.io.Files;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.*;
@ -60,6 +67,11 @@ public class FlatFileDatabaseManagerTest {
int expectedScoreboardTips = 1111;
Long expectedLastLogin = 2020L;
@BeforeAll
static void initBeforeAll() {
logger.setFilter(new DebugFilter());
}
@BeforeEach
public void init() {
assertNull(db);
@ -175,7 +187,7 @@ public class FlatFileDatabaseManagerTest {
//This makes sure our private method is working before the tests run afterwards
ArrayList<String[]> dataFromFile = getSplitDataFromFile(dbFile);
System.out.println("File Path: "+ dbFile.getAbsolutePath());
logger.info("File Path: "+ dbFile.getAbsolutePath());
assertArrayEquals(LINE_TWO_FROM_MISSING_DB.split(":"), dataFromFile.get(1));
assertEquals(dataFromFile.get(1)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
@ -199,7 +211,7 @@ public class FlatFileDatabaseManagerTest {
//This makes sure our private method is working before the tests run afterwards
ArrayList<String[]> dataFromFile = getSplitDataFromFile(healthyDB);
System.out.println("File Path: "+healthyDB.getAbsolutePath());
logger.info("File Path: "+healthyDB.getAbsolutePath());
assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
UUID healthDBEntryOneUUID = UUID.fromString(HEALTHY_DB_LINE_ONE_UUID_STR);
@ -270,7 +282,7 @@ public class FlatFileDatabaseManagerTest {
assertEquals(playerName, playerProfile.getPlayerName());
assertEquals(uuid, playerProfile.getUniqueId());
PlayerProfile retrievedFromDisk = db.loadPlayerProfile(uuid, playerName);
PlayerProfile retrievedFromDisk = db.loadPlayerProfile(uuid);
assertTrue(retrievedFromDisk.isLoaded());
assertEquals(playerName, retrievedFromDisk.getPlayerName());
assertEquals(uuid, retrievedFromDisk.getUniqueId());
@ -320,7 +332,7 @@ public class FlatFileDatabaseManagerTest {
//This makes sure our private method is working before the tests run afterwards
ArrayList<String[]> dataFromFile = getSplitDataFromFile(dbFile);
System.out.println("File Path: " + dbFile.getAbsolutePath());
logger.info("File Path: " + dbFile.getAbsolutePath());
assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
@ -335,16 +347,50 @@ public class FlatFileDatabaseManagerTest {
String playerName = "nossr50";
UUID uuid = UUID.fromString("588fe472-1c82-4c4e-9aa1-7eefccb277e3");
PlayerProfile profile1 = db.loadPlayerProfile(uuid, null);
PlayerProfile profile2 = db.loadPlayerProfile(uuid, playerName);
PlayerProfile profile3 = db.loadPlayerProfile(uuid, "incorrectName");
PlayerProfile profile4 = db.loadPlayerProfile(new UUID(0, 1), "shouldBeUnloaded");
assertFalse(profile4.isLoaded());
//Three possible ways to load the thing
PlayerProfile profile1 = db.loadPlayerProfile(uuid);
testHealthyDataProfileValues(playerName, uuid, profile1);
testHealthyDataProfileValues(playerName, uuid, profile2);
testHealthyDataProfileValues(playerName, uuid, profile3);
assertFalse(db.loadPlayerProfile(new UUID(0, 1)).isLoaded()); //This profile should not exist and therefor will return unloaded
}
@Test
public void testLoadByUUIDAndName() {
File dbFile = prepareDatabaseTestResource(DB_HEALTHY);
/*
* We have established the files are in good order, so now for the actual testing
*/
//This makes sure our private method is working before the tests run afterwards
ArrayList<String[]> dataFromFile = getSplitDataFromFile(dbFile);
logger.info("File Path: " + dbFile.getAbsolutePath());
assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
db = new FlatFileDatabaseManager(dbFile, logger, PURGE_TIME, 0, true);
List<FlatFileDataFlag> flagsFound = db.checkFileHealthAndStructure();
assertNull(flagsFound); //No flags should be found
/*
* Once the DB looks fine load the profile
*/
String playerName = "nossr50";
UUID uuid = UUID.fromString("588fe472-1c82-4c4e-9aa1-7eefccb277e3");
TestOfflinePlayer player = new TestOfflinePlayer(playerName, uuid);
PlayerProfile profile1 = db.loadPlayerProfile(player);
testHealthyDataProfileValues(playerName, uuid, profile1);
String updatedName = "updatedName";
TestOfflinePlayer updatedNamePlayer = new TestOfflinePlayer(updatedName, uuid);
PlayerProfile updatedNameProfile = db.loadPlayerProfile(updatedNamePlayer);
testHealthyDataProfileValues(updatedName, uuid, updatedNameProfile);
TestOfflinePlayer shouldNotExist = new TestOfflinePlayer("doesntexist", new UUID(0, 1));
PlayerProfile profile3 = db.loadPlayerProfile(shouldNotExist);
assertFalse(profile3.isLoaded());
}
private File prepareDatabaseTestResource(@NotNull String dbFileName) {
@ -393,11 +439,11 @@ public class FlatFileDatabaseManagerTest {
if(SkillTools.isChildSkill(primarySkillType))
continue;
// System.out.println("Checking expected values for: "+primarySkillType);
// System.out.println("Profile Level Value: "+profile.getSkillLevel(primarySkillType));
// System.out.println("Expected Lvl Value: "+getExpectedLevelHealthyDBEntryOne(primarySkillType));
// System.out.println("Profile Exp Value: "+profile.getSkillXpLevelRaw(primarySkillType));
// System.out.println("Expected Exp Value: "+getExpectedExperienceHealthyDBEntryOne(primarySkillType));
// logger.info("Checking expected values for: "+primarySkillType);
// logger.info("Profile Level Value: "+profile.getSkillLevel(primarySkillType));
// logger.info("Expected Lvl Value: "+getExpectedLevelHealthyDBEntryOne(primarySkillType));
// logger.info("Profile Exp Value: "+profile.getSkillXpLevelRaw(primarySkillType));
// logger.info("Expected Exp Value: "+getExpectedExperienceHealthyDBEntryOne(primarySkillType));
assertEquals(getExpectedLevelHealthyDBEntryOne(primarySkillType), profile.getSkillLevel(primarySkillType));
assertEquals(getExpectedExperienceHealthyDBEntryOne(primarySkillType), profile.getSkillXpLevelRaw(primarySkillType), 0);
@ -640,7 +686,7 @@ public class FlatFileDatabaseManagerTest {
//This makes sure our private method is working before the tests run afterwards
ArrayList<String[]> dataFromFile = getSplitDataFromFile(copyOfFile);
System.out.println("File Path: "+copyOfFile.getAbsolutePath());
logger.info("File Path: "+copyOfFile.getAbsolutePath());
assertArrayEquals(BAD_FILE_LINE_ONE.split(":"), dataFromFile.get(0));
assertEquals(dataFromFile.get(22)[0], "nossr51");
assertArrayEquals(BAD_DATA_FILE_LINE_TWENTY_THREE.split(":"), dataFromFile.get(22));
@ -688,7 +734,7 @@ public class FlatFileDatabaseManagerTest {
out.write(writer.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("File not found");
logger.info("File not found");
} catch (IOException e) {
e.printStackTrace();
} finally {
@ -703,12 +749,12 @@ public class FlatFileDatabaseManagerTest {
}
try {
System.out.println("Added the following lines to the FlatFileDatabase for the purposes of the test...");
logger.info("Added the following lines to the FlatFileDatabase for the purposes of the test...");
// Open the file
in = new BufferedReader(new FileReader(filePath));
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
logger.info(line);
}
} catch (IOException e) {
e.printStackTrace();
@ -731,4 +777,188 @@ public class FlatFileDatabaseManagerTest {
assertNotNull(dataFlags);
assertTrue(dataFlags.contains(flag));
}
private class TestOfflinePlayer implements OfflinePlayer {
private final @NotNull String name;
private final @NotNull UUID uuid;
private TestOfflinePlayer(@NotNull String name, @NotNull UUID uuid) {
this.name = name;
this.uuid = uuid;
}
@Override
public boolean isOnline() {
return false;
}
@Nullable
@Override
public String getName() {
return name;
}
@NotNull
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public boolean isBanned() {
return false;
}
@Override
public boolean isWhitelisted() {
return false;
}
@Override
public void setWhitelisted(boolean value) {
}
@Nullable
@Override
public Player getPlayer() {
return null;
}
@Override
public long getFirstPlayed() {
return 0;
}
@Override
public long getLastPlayed() {
return 0;
}
@Override
public boolean hasPlayedBefore() {
return false;
}
@Nullable
@Override
public Location getBedSpawnLocation() {
return null;
}
@Override
public void incrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
}
@Override
public void decrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
}
@Override
public void incrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException {
}
@Override
public void decrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException {
}
@Override
public void setStatistic(@NotNull Statistic statistic, int newValue) throws IllegalArgumentException {
}
@Override
public int getStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
return 0;
}
@Override
public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
}
@Override
public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
}
@Override
public int getStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
return 0;
}
@Override
public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException {
}
@Override
public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException {
}
@Override
public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) throws IllegalArgumentException {
}
@Override
public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
}
@Override
public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
}
@Override
public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
return 0;
}
@Override
public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) throws IllegalArgumentException {
}
@Override
public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {
}
@Override
public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) {
}
@NotNull
@Override
public Map<String, Object> serialize() {
return null;
}
@Override
public boolean isOp() {
return false;
}
@Override
public void setOp(boolean value) {
}
}
private static class DebugFilter implements Filter {
@Override
public boolean isLoggable(LogRecord record) {
return false;
}
}
}