mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-11-22 18:25:27 +01:00
#1125 Create infrastructure for Limbo persistence + restore 5.2 JSON storage
- Introduce configurable storage mechanism - LimboPersistence wraps a LimboPersistenceHandler, of which there are multiple implementations - Outside of the limbo.persistence package, classes only talk to LimboPersistence - Restore the way of persisting to JSON from 5.2 (SeparateFilePersistenceHandler) - Add handling for stored limbo players - Merge any existing LimboPlayers together with the goal of only keeping one version of a LimboPlayer: there is no way for a player to be online without triggering the creation of a LimboPlayer first, so we can guarantee that the in-memory LimboPlayer is the most up-to-date, i.e. when restoring limbo data we don't have to check against the disk. - Create and delete LimboPlayers at the same time when LimboPlayers are added or removed from the in-memory map - Catch all exceptions in LimboPersistence so a handler throwing an unexpected exception does not stop the limbo process (#1070) - Extend debug command /authme debug limbo to show LimboPlayer information on disk, too
This commit is contained in:
parent
1678901e02
commit
8557621c02
@ -159,6 +159,7 @@
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="ignore|ignored"/>
|
||||
</module>
|
||||
<module name="MissingOverride"/>
|
||||
</module>
|
||||
<module name="FileTabCharacter"/>
|
||||
</module>
|
||||
|
@ -6,10 +6,10 @@ import fr.xephi.authme.initialization.factory.Factory;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Debug command main.
|
||||
@ -47,7 +47,7 @@ public class DebugCommand implements ExecutableCommand {
|
||||
// Lazy getter
|
||||
private Map<String, DebugSection> getSections() {
|
||||
if (sections == null) {
|
||||
Map<String, DebugSection> sections = new HashMap<>();
|
||||
Map<String, DebugSection> sections = new TreeMap<>();
|
||||
for (Class<? extends DebugSection> sectionClass : sectionClasses) {
|
||||
DebugSection section = debugSectionFactory.newInstance(sectionClass);
|
||||
sections.put(section.getName(), section);
|
||||
|
@ -3,6 +3,7 @@ package fr.xephi.authme.command.executable.authme.debug;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -27,6 +28,9 @@ class LimboPlayerViewer implements DebugSection {
|
||||
@Inject
|
||||
private LimboService limboService;
|
||||
|
||||
@Inject
|
||||
private LimboPersistence limboPersistence;
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@ -50,22 +54,23 @@ class LimboPlayerViewer implements DebugSection {
|
||||
return;
|
||||
}
|
||||
|
||||
LimboPlayer limbo = limboService.getLimboPlayer(arguments.get(0));
|
||||
LimboPlayer memoryLimbo = limboService.getLimboPlayer(arguments.get(0));
|
||||
Player player = bukkitService.getPlayerExact(arguments.get(0));
|
||||
if (limbo == null && player == null) {
|
||||
LimboPlayer diskLimbo = player != null ? limboPersistence.getLimboPlayer(player) : null;
|
||||
if (memoryLimbo == null && player == null) {
|
||||
sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "Showing limbo / player info for '" + arguments.get(0) + "'");
|
||||
new InfoDisplayer(sender, limbo, player)
|
||||
sender.sendMessage(ChatColor.GOLD + "Showing disk limbo / limbo / player info for '" + arguments.get(0) + "'");
|
||||
new InfoDisplayer(sender, diskLimbo, memoryLimbo, player)
|
||||
.sendEntry("Is op", LimboPlayer::isOperator, Player::isOp)
|
||||
.sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed)
|
||||
.sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight)
|
||||
.sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed)
|
||||
.sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation()))
|
||||
.sendEntry("Group", LimboPlayer::getGroup, p -> "");
|
||||
sender.sendMessage("Note: group is only shown for LimboPlayer");
|
||||
sender.sendMessage("Note: group is not shown for Player. Use /authme debug groups");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,25 +107,30 @@ class LimboPlayerViewer implements DebugSection {
|
||||
*/
|
||||
private static final class InfoDisplayer {
|
||||
private final CommandSender sender;
|
||||
private final Optional<LimboPlayer> limbo;
|
||||
private final Optional<LimboPlayer> diskLimbo;
|
||||
private final Optional<LimboPlayer> memoryLimbo;
|
||||
private final Optional<Player> player;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param sender command sender to send the information to
|
||||
* @param limbo the limbo player to get data from
|
||||
* @param memoryLimbo the limbo player to get data from
|
||||
* @param player the player to get data from
|
||||
*/
|
||||
InfoDisplayer(CommandSender sender, LimboPlayer limbo, Player player) {
|
||||
InfoDisplayer(CommandSender sender, LimboPlayer diskLimbo, LimboPlayer memoryLimbo, Player player) {
|
||||
this.sender = sender;
|
||||
this.limbo = Optional.ofNullable(limbo);
|
||||
this.diskLimbo = Optional.ofNullable(diskLimbo);
|
||||
this.memoryLimbo = Optional.ofNullable(memoryLimbo);
|
||||
this.player = Optional.ofNullable(player);
|
||||
|
||||
if (limbo == null) {
|
||||
if (memoryLimbo == null) {
|
||||
sender.sendMessage("Note: no Limbo information available");
|
||||
} else if (player == null) {
|
||||
}
|
||||
if (player == null) {
|
||||
sender.sendMessage("Note: player is not online");
|
||||
} else if (diskLimbo == null) {
|
||||
sender.sendMessage("Note: no Limbo on disk available");
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,10 +148,16 @@ class LimboPlayerViewer implements DebugSection {
|
||||
Function<Player, T> playerGetter) {
|
||||
sender.sendMessage(
|
||||
title + ": "
|
||||
+ limbo.map(limboGetter).map(String::valueOf).orElse("--")
|
||||
+ getData(diskLimbo, limboGetter)
|
||||
+ " / "
|
||||
+ player.map(playerGetter).map(String::valueOf).orElse("--"));
|
||||
+ getData(memoryLimbo, limboGetter)
|
||||
+ " / "
|
||||
+ getData(player, playerGetter));
|
||||
return this;
|
||||
}
|
||||
|
||||
static <E, T> String getData(Optional<E> entity, Function<E, T> getter) {
|
||||
return entity.map(getter).map(String::valueOf).orElse(" -- ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@ -28,7 +29,10 @@ public class LimboService {
|
||||
private LimboPlayerTaskManager taskManager;
|
||||
|
||||
@Inject
|
||||
private LimboServiceHelper limboServiceHelper;
|
||||
private LimboServiceHelper helper;
|
||||
|
||||
@Inject
|
||||
private LimboPersistence persistence;
|
||||
|
||||
LimboService() {
|
||||
}
|
||||
@ -42,19 +46,25 @@ public class LimboService {
|
||||
public void createLimboPlayer(Player player, boolean isRegistered) {
|
||||
final String name = player.getName().toLowerCase();
|
||||
|
||||
LimboPlayer limboFromDisk = persistence.getLimboPlayer(player);
|
||||
if (limboFromDisk != null) {
|
||||
ConsoleLogger.debug("LimboPlayer for `{0}` already exists on disk", name);
|
||||
}
|
||||
|
||||
LimboPlayer existingLimbo = entries.remove(name);
|
||||
if (existingLimbo != null) {
|
||||
existingLimbo.clearTasks();
|
||||
ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name);
|
||||
ConsoleLogger.debug("LimboPlayer for `{0}` already present in memory", name);
|
||||
}
|
||||
|
||||
LimboPlayer limboPlayer = limboServiceHelper.merge(
|
||||
limboServiceHelper.createLimboPlayer(player, isRegistered), existingLimbo);
|
||||
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
|
||||
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer);
|
||||
|
||||
taskManager.registerMessageTask(player, limboPlayer, isRegistered);
|
||||
taskManager.registerTimeoutTask(player, limboPlayer);
|
||||
limboServiceHelper.revokeLimboStates(player);
|
||||
helper.revokeLimboStates(player);
|
||||
entries.put(name, limboPlayer);
|
||||
persistence.saveLimboPlayer(player, limboPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,6 +108,7 @@ public class LimboService {
|
||||
settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo);
|
||||
limbo.clearTasks();
|
||||
ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName);
|
||||
persistence.removeLimboPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,8 +69,7 @@ class LimboServiceHelper {
|
||||
* <ul>
|
||||
* <li><code>isOperator, allowFlight</code>: true if either limbo has true</li>
|
||||
* <li><code>flySpeed, walkSpeed</code>: maximum value of either limbo player</li>
|
||||
* <li><code>group</code>: from old limbo if not empty, otherwise from new limbo</li>
|
||||
* <li><code>location</code>: from old limbo</li>
|
||||
* <li><code>group, location</code>: from old limbo if not empty/null, otherwise from new limbo</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param newLimbo the new limbo player
|
||||
@ -89,8 +88,9 @@ class LimboServiceHelper {
|
||||
float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed());
|
||||
float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed());
|
||||
String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup());
|
||||
Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation());
|
||||
|
||||
return new LimboPlayer(oldLimbo.getLocation(), isOperator, group, canFly, walkSpeed, flySpeed);
|
||||
return new LimboPlayer(location, isOperator, group, canFly, walkSpeed, flySpeed);
|
||||
}
|
||||
|
||||
private static String firstNotEmpty(String newGroup, String oldGroup) {
|
||||
@ -100,4 +100,8 @@ class LimboServiceHelper {
|
||||
}
|
||||
return oldGroup;
|
||||
}
|
||||
|
||||
private static Location firstNotNull(Location first, Location second) {
|
||||
return first == null ? second : first;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.SettingsDependent;
|
||||
import fr.xephi.authme.initialization.factory.Factory;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.LimboSettings;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Handles the persistence of LimboPlayers.
|
||||
*/
|
||||
public class LimboPersistence implements SettingsDependent {
|
||||
|
||||
private final Factory<LimboPersistenceHandler> handlerFactory;
|
||||
|
||||
private LimboPersistenceHandler handler;
|
||||
|
||||
@Inject
|
||||
LimboPersistence(Settings settings, Factory<LimboPersistenceHandler> handlerFactory) {
|
||||
this.handlerFactory = handlerFactory;
|
||||
reload(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the LimboPlayer for the given player if available.
|
||||
*
|
||||
* @param player the player to retrieve the LimboPlayer for
|
||||
* @return the player's limbo player, or null if not available
|
||||
*/
|
||||
public LimboPlayer getLimboPlayer(Player player) {
|
||||
try {
|
||||
return handler.getLimboPlayer(player);
|
||||
} catch (Exception e) {
|
||||
ConsoleLogger.logException("Could not get LimboPlayer for '" + player.getName() + "'", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given LimboPlayer for the provided player.
|
||||
*
|
||||
* @param player the player to save the LimboPlayer for
|
||||
* @param limbo the limbo player to save
|
||||
*/
|
||||
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
|
||||
try {
|
||||
handler.saveLimboPlayer(player, limbo);
|
||||
} catch (Exception e) {
|
||||
ConsoleLogger.logException("Could not save LimboPlayer for '" + player.getName() + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the LimboPlayer for the given player.
|
||||
*
|
||||
* @param player the player whose LimboPlayer should be removed
|
||||
*/
|
||||
public void removeLimboPlayer(Player player) {
|
||||
try {
|
||||
handler.removeLimboPlayer(player);
|
||||
} catch (Exception e) {
|
||||
ConsoleLogger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(Settings settings) {
|
||||
LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE);
|
||||
if (handler == null || handler.getType() != persistenceType) {
|
||||
// If we're changing from an existing handler, output a quick hint that nothing is converted.
|
||||
if (handler != null) {
|
||||
ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted.");
|
||||
}
|
||||
|
||||
handler = handlerFactory.newInstance(persistenceType.getImplementationClass());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Handles I/O for storing LimboPlayer objects.
|
||||
*/
|
||||
interface LimboPersistenceHandler {
|
||||
|
||||
/**
|
||||
* Returns the limbo player for the given player if it exists.
|
||||
*
|
||||
* @param player the player
|
||||
* @return the stored limbo player, or null if not available
|
||||
*/
|
||||
LimboPlayer getLimboPlayer(Player player);
|
||||
|
||||
/**
|
||||
* Saves the given limbo player for the given player to the disk.
|
||||
*
|
||||
* @param player the player to save the limbo player for
|
||||
* @param limbo the limbo player to save
|
||||
*/
|
||||
void saveLimboPlayer(Player player, LimboPlayer limbo);
|
||||
|
||||
/**
|
||||
* Removes the limbo player from the disk.
|
||||
*
|
||||
* @param player the player whose limbo player should be removed
|
||||
*/
|
||||
void removeLimboPlayer(Player player);
|
||||
|
||||
/**
|
||||
* @return the type of the limbo persistence implementation
|
||||
*/
|
||||
LimboPersistenceType getType();
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
/**
|
||||
* Types of persistence for LimboPlayer objects.
|
||||
*/
|
||||
public enum LimboPersistenceType {
|
||||
|
||||
INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class),
|
||||
|
||||
DISABLED(NoOpPersistenceHandler.class);
|
||||
|
||||
private final Class<? extends LimboPersistenceHandler> implementationClass;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param implementationClass the implementation class
|
||||
*/
|
||||
LimboPersistenceType(Class<? extends LimboPersistenceHandler> implementationClass) {
|
||||
this.implementationClass= implementationClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class implementing the persistence type
|
||||
*/
|
||||
public Class<? extends LimboPersistenceHandler> getImplementationClass() {
|
||||
return implementationClass;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Limbo player persistence implementation that does nothing.
|
||||
*/
|
||||
class NoOpPersistenceHandler implements LimboPersistenceHandler {
|
||||
|
||||
@Override
|
||||
public LimboPlayer getLimboPlayer(Player player) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLimboPlayer(Player player) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPersistenceType getType() {
|
||||
return LimboPersistenceType.DISABLED;
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.DataFolder;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Saves LimboPlayer objects as JSON into individual files.
|
||||
*/
|
||||
class SeparateFilePersistenceHandler implements LimboPersistenceHandler {
|
||||
|
||||
private final Gson gson;
|
||||
private final File cacheDir;
|
||||
private final BukkitService bukkitService;
|
||||
|
||||
@Inject
|
||||
SeparateFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
|
||||
this.bukkitService = bukkitService;
|
||||
|
||||
cacheDir = new File(dataFolder, "playerdata");
|
||||
if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) {
|
||||
ConsoleLogger.warning("Failed to create userdata directory.");
|
||||
}
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
|
||||
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer())
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPlayer getLimboPlayer(Player player) {
|
||||
String id = PlayerUtils.getUUIDorName(player);
|
||||
File file = new File(cacheDir, id + File.separator + "data.json");
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String str = Files.toString(file, StandardCharsets.UTF_8);
|
||||
return gson.fromJson(str, LimboPlayer.class);
|
||||
} catch (IOException e) {
|
||||
ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveLimboPlayer(Player player, LimboPlayer limboPlayer) {
|
||||
String id = PlayerUtils.getUUIDorName(player);
|
||||
try {
|
||||
File file = new File(cacheDir, id + File.separator + "data.json");
|
||||
Files.createParentDirs(file);
|
||||
Files.touch(file);
|
||||
Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
ConsoleLogger.logException("Failed to write " + player.getName() + " data:", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the LimboPlayer. This will delete the
|
||||
* "playerdata/<uuid or name>/" folder from disk.
|
||||
*
|
||||
* @param player player to remove
|
||||
*/
|
||||
@Override
|
||||
public void removeLimboPlayer(Player player) {
|
||||
String id = PlayerUtils.getUUIDorName(player);
|
||||
File file = new File(cacheDir, id);
|
||||
if (file.exists()) {
|
||||
FileUtils.purgeDirectory(file);
|
||||
FileUtils.delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPersistenceType getType() {
|
||||
return LimboPersistenceType.INDIVIDUAL_FILES;
|
||||
}
|
||||
|
||||
private final class LimboPlayerDeserializer implements JsonDeserializer<LimboPlayer> {
|
||||
|
||||
@Override
|
||||
public LimboPlayer deserialize(JsonElement jsonElement, Type type,
|
||||
JsonDeserializationContext context) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
if (jsonObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Location loc = null;
|
||||
String group = "";
|
||||
boolean operator = false;
|
||||
boolean canFly = false;
|
||||
float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED;
|
||||
float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED;
|
||||
|
||||
JsonElement e;
|
||||
if ((e = jsonObject.getAsJsonObject("location")) != null) {
|
||||
JsonObject obj = e.getAsJsonObject();
|
||||
World world = bukkitService.getWorld(obj.get("world").getAsString());
|
||||
if (world != null) {
|
||||
double x = obj.get("x").getAsDouble();
|
||||
double y = obj.get("y").getAsDouble();
|
||||
double z = obj.get("z").getAsDouble();
|
||||
float yaw = obj.get("yaw").getAsFloat();
|
||||
float pitch = obj.get("pitch").getAsFloat();
|
||||
loc = new Location(world, x, y, z, yaw, pitch);
|
||||
}
|
||||
}
|
||||
if ((e = jsonObject.get("group")) != null) {
|
||||
group = e.getAsString();
|
||||
}
|
||||
if ((e = jsonObject.get("operator")) != null) {
|
||||
operator = e.getAsBoolean();
|
||||
}
|
||||
if ((e = jsonObject.get("can-fly")) != null) {
|
||||
canFly = e.getAsBoolean();
|
||||
}
|
||||
if ((e = jsonObject.get("walk-speed")) != null) {
|
||||
walkSpeed = e.getAsFloat();
|
||||
}
|
||||
if ((e = jsonObject.get("fly-speed")) != null) {
|
||||
flySpeed = e.getAsFloat();
|
||||
}
|
||||
|
||||
return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LimboPlayerSerializer implements JsonSerializer<LimboPlayer> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(LimboPlayer limboPlayer, Type type,
|
||||
JsonSerializationContext context) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("group", limboPlayer.getGroup());
|
||||
|
||||
Location loc = limboPlayer.getLocation();
|
||||
JsonObject obj2 = new JsonObject();
|
||||
obj2.addProperty("world", loc.getWorld().getName());
|
||||
obj2.addProperty("x", loc.getX());
|
||||
obj2.addProperty("y", loc.getY());
|
||||
obj2.addProperty("z", loc.getZ());
|
||||
obj2.addProperty("yaw", loc.getYaw());
|
||||
obj2.addProperty("pitch", loc.getPitch());
|
||||
obj.add("location", obj2);
|
||||
|
||||
obj.addProperty("operator", limboPlayer.isOperator());
|
||||
obj.addProperty("can-fly", limboPlayer.isCanFly());
|
||||
obj.addProperty("walk-speed", limboPlayer.getWalkSpeed());
|
||||
obj.addProperty("fly-speed", limboPlayer.getFlySpeed());
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import ch.jalu.configme.properties.Property;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import fr.xephi.authme.data.limbo.AllowFlightRestoreType;
|
||||
import fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType;
|
||||
import fr.xephi.authme.data.limbo.persistence.LimboPersistenceType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -17,6 +18,15 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
|
||||
*/
|
||||
public final class LimboSettings implements SettingsHolder {
|
||||
|
||||
@Comment({
|
||||
"Besides storing the data in memory, you can define if/how the data should be persisted",
|
||||
"on disk. This is useful in case of a server crash, so next time the server starts we can",
|
||||
"properly restore things like OP status, ability to fly, and walk/fly speed.",
|
||||
"DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file"
|
||||
})
|
||||
public static final Property<LimboPersistenceType> LIMBO_PERSISTENCE_TYPE =
|
||||
newProperty(LimboPersistenceType.class, "limbo.persistence", LimboPersistenceType.INDIVIDUAL_FILES);
|
||||
|
||||
@Comment({
|
||||
"Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.",
|
||||
"RESTORE sets back the old property from the player."
|
||||
@ -50,7 +60,7 @@ public final class LimboSettings implements SettingsHolder {
|
||||
"Before a user logs in, various properties are temporarily removed from the player,",
|
||||
"such as OP status, ability to fly, and walk/fly speed.",
|
||||
"Once the user is logged in, we add back the properties we previously saved.",
|
||||
"In this section, you may define how the properties should be restored."
|
||||
"In this section, you may define how these properties should be handled."
|
||||
};
|
||||
return ImmutableMap.of("limbo", limboExplanation);
|
||||
}
|
||||
|
@ -48,14 +48,13 @@ public class LimboServiceHelperTest {
|
||||
// given
|
||||
Location newLocation = mock(Location.class);
|
||||
LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", true, 0.3f, 0.0f);
|
||||
Location oldLocation = mock(Location.class);
|
||||
LimboPlayer oldLimbo = new LimboPlayer(oldLocation, false, "", false, 0.1f, 0.1f);
|
||||
LimboPlayer oldLimbo = new LimboPlayer(null, false, "", false, 0.1f, 0.1f);
|
||||
|
||||
// when
|
||||
LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo);
|
||||
|
||||
// then
|
||||
assertThat(result.getLocation(), equalTo(oldLocation));
|
||||
assertThat(result.getLocation(), equalTo(newLocation));
|
||||
assertThat(result.isOperator(), equalTo(false));
|
||||
assertThat(result.getGroup(), equalTo("grp-new"));
|
||||
assertThat(result.isCanFly(), equalTo(true));
|
||||
|
@ -4,6 +4,7 @@ import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.ReflectionTestUtils;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.SpawnLoader;
|
||||
@ -57,6 +58,9 @@ public class LimboServiceTest {
|
||||
@Mock
|
||||
private LimboPlayerTaskManager taskManager;
|
||||
|
||||
@Mock
|
||||
private LimboPersistence limboPersistence;
|
||||
|
||||
@BeforeClass
|
||||
public static void initLogger() {
|
||||
TestHelper.setupLogger();
|
||||
|
@ -0,0 +1,175 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import ch.jalu.injector.testing.BeforeInjecting;
|
||||
import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.ReflectionTestUtils;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.factory.Factory;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.LimboSettings;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
|
||||
|
||||
/**
|
||||
* Test for {@link LimboPersistence}.
|
||||
*/
|
||||
@RunWith(DelayedInjectionRunner.class)
|
||||
public class LimboPersistenceTest {
|
||||
|
||||
@InjectDelayed
|
||||
private LimboPersistence limboPersistence;
|
||||
|
||||
@Mock
|
||||
private Factory<LimboPersistenceHandler> handlerFactory;
|
||||
|
||||
@Mock
|
||||
private Settings settings;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpLogger() {
|
||||
TestHelper.setupLogger();
|
||||
}
|
||||
|
||||
@BeforeInjecting
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUpMocks() {
|
||||
given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE)).willReturn(LimboPersistenceType.DISABLED);
|
||||
given(handlerFactory.newInstance(any(Class.class)))
|
||||
.willAnswer(invocation -> mock(invocation.getArgument(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeProperly() {
|
||||
// given / when / then
|
||||
assertThat(getHandler(), instanceOf(NoOpPersistenceHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDelegateToHandler() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
LimboPlayer limbo = mock(LimboPlayer.class);
|
||||
given(handler.getLimboPlayer(player)).willReturn(limbo);
|
||||
|
||||
// when
|
||||
LimboPlayer result = limboPersistence.getLimboPlayer(player);
|
||||
limboPersistence.saveLimboPlayer(player, mock(LimboPlayer.class));
|
||||
limboPersistence.removeLimboPlayer(mock(Player.class));
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(limbo));
|
||||
verify(handler).getLimboPlayer(player);
|
||||
verify(handler).saveLimboPlayer(eq(player), argThat(notNullAndDifferentFrom(limbo)));
|
||||
verify(handler).removeLimboPlayer(argThat(notNullAndDifferentFrom(player)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReloadProperly() {
|
||||
// given
|
||||
given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE))
|
||||
.willReturn(LimboPersistenceType.INDIVIDUAL_FILES);
|
||||
|
||||
// when
|
||||
limboPersistence.reload(settings);
|
||||
|
||||
// then
|
||||
assertThat(getHandler(), instanceOf(LimboPersistenceType.INDIVIDUAL_FILES.getImplementationClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotReinitializeHandlerForSameType() {
|
||||
// given
|
||||
LimboPersistenceHandler currentHandler = getHandler();
|
||||
Mockito.reset(handlerFactory);
|
||||
given(currentHandler.getType()).willCallRealMethod();
|
||||
|
||||
// when
|
||||
limboPersistence.reload(settings);
|
||||
|
||||
// then
|
||||
verifyZeroInteractions(handlerFactory);
|
||||
assertThat(currentHandler, sameInstance(getHandler()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionWhenGettingLimbo() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
Logger logger = TestHelper.setupLogger();
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
doThrow(IllegalAccessException.class).when(handler).getLimboPlayer(player);
|
||||
|
||||
// when
|
||||
LimboPlayer result = limboPersistence.getLimboPlayer(player);
|
||||
|
||||
// then
|
||||
assertThat(result, nullValue());
|
||||
verify(logger).warning(argThat(containsString("[IllegalAccessException]")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionWhenSavingLimbo() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
LimboPlayer limbo = mock(LimboPlayer.class);
|
||||
Logger logger = TestHelper.setupLogger();
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
doThrow(IllegalStateException.class).when(handler).saveLimboPlayer(player, limbo);
|
||||
|
||||
// when
|
||||
limboPersistence.saveLimboPlayer(player, limbo);
|
||||
|
||||
// then
|
||||
verify(logger).warning(argThat(containsString("[IllegalStateException]")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionWhenRemovingLimbo() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
Logger logger = TestHelper.setupLogger();
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
doThrow(UnsupportedOperationException.class).when(handler).removeLimboPlayer(player);
|
||||
|
||||
// when
|
||||
limboPersistence.removeLimboPlayer(player);
|
||||
|
||||
// then
|
||||
verify(logger).warning(argThat(containsString("[UnsupportedOperationException]")));
|
||||
}
|
||||
|
||||
private LimboPersistenceHandler getHandler() {
|
||||
return ReflectionTestUtils.getFieldValue(LimboPersistence.class, limboPersistence, "handler");
|
||||
}
|
||||
|
||||
private static <T> Matcher<T> notNullAndDifferentFrom(T o) {
|
||||
return both(not(sameInstance(o))).and(not(nullValue()));
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import ch.jalu.injector.testing.BeforeInjecting;
|
||||
import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.DataFolder;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Test for {@link SeparateFilePersistenceHandler}.
|
||||
*/
|
||||
@RunWith(DelayedInjectionRunner.class)
|
||||
public class SeparateFilePersistenceHandlerTest {
|
||||
|
||||
private static final UUID SAMPLE_UUID = UUID.nameUUIDFromBytes("PersistenceTest".getBytes());
|
||||
private static final String SOURCE_FOLDER = TestHelper.PROJECT_ROOT + "data/backup/";
|
||||
|
||||
@InjectDelayed
|
||||
private SeparateFilePersistenceHandler handler;
|
||||
|
||||
@Mock
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@DataFolder
|
||||
private File dataFolder;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@BeforeInjecting
|
||||
public void copyTestFiles() throws IOException {
|
||||
dataFolder = temporaryFolder.newFolder();
|
||||
File playerFolder = new File(dataFolder, FileUtils.makePath("playerdata", SAMPLE_UUID.toString()));
|
||||
if (!playerFolder.mkdirs()) {
|
||||
throw new IllegalStateException("Cannot create '" + playerFolder.getAbsolutePath() + "'");
|
||||
}
|
||||
Files.copy(TestHelper.getJarPath(FileUtils.makePath(SOURCE_FOLDER, "sample-folder", "data.json")),
|
||||
new File(playerFolder, "data.json").toPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadDataFromFile() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
given(player.getUniqueId()).willReturn(SAMPLE_UUID);
|
||||
World world = mock(World.class);
|
||||
given(bukkitService.getWorld("nether")).willReturn(world);
|
||||
|
||||
// when
|
||||
LimboPlayer data = handler.getLimboPlayer(player);
|
||||
|
||||
// then
|
||||
assertThat(data, not(nullValue()));
|
||||
assertThat(data.isOperator(), equalTo(true));
|
||||
assertThat(data.isCanFly(), equalTo(true));
|
||||
assertThat(data.getWalkSpeed(), equalTo(0.2f));
|
||||
assertThat(data.getFlySpeed(), equalTo(0.1f));
|
||||
assertThat(data.getGroup(), equalTo("players"));
|
||||
Location location = data.getLocation();
|
||||
assertThat(location.getX(), equalTo(-113.219));
|
||||
assertThat(location.getY(), equalTo(72.0));
|
||||
assertThat(location.getZ(), equalTo(130.637));
|
||||
assertThat(location.getWorld(), equalTo(world));
|
||||
assertThat(location.getPitch(), equalTo(24.15f));
|
||||
assertThat(location.getYaw(), equalTo(-292.484f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnNullForUnavailablePlayer() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
given(player.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("other-player".getBytes()));
|
||||
|
||||
// when
|
||||
LimboPlayer data = handler.getLimboPlayer(player);
|
||||
|
||||
// then
|
||||
assertThat(data, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSavePlayerData() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
UUID uuid = UUID.nameUUIDFromBytes("New player".getBytes());
|
||||
given(player.getUniqueId()).willReturn(uuid);
|
||||
|
||||
|
||||
World world = mock(World.class);
|
||||
given(world.getName()).willReturn("player-world");
|
||||
Location location = new Location(world, 0.2, 102.25, -89.28, 3.02f, 90.13f);
|
||||
String group = "primary-grp";
|
||||
LimboPlayer limbo = new LimboPlayer(location, true, group, true, 1.2f, 0.8f);
|
||||
|
||||
// when
|
||||
handler.saveLimboPlayer(player, limbo);
|
||||
|
||||
// then
|
||||
File playerFile = new File(dataFolder, FileUtils.makePath("playerdata", uuid.toString(), "data.json"));
|
||||
assertThat(playerFile.exists(), equalTo(true));
|
||||
// TODO ljacqu 20160711: Check contents of file
|
||||
}
|
||||
|
||||
}
|
@ -22,7 +22,7 @@ public class AuthMeSettingsRetrieverTest {
|
||||
// an error margin of 10: this prevents us from having to adjust the test every time the config is changed.
|
||||
// If this test fails, replace the first argument in closeTo() with the new number of properties
|
||||
assertThat((double) configurationData.getProperties().size(),
|
||||
closeTo(150, 10));
|
||||
closeTo(160, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user