#1125 Create SingleFilePersistenceHandler + extract (de)serializer for LimboPlayer

This commit is contained in:
ljacqu 2017-03-13 18:30:52 +01:00
parent 8557621c02
commit 710198833c
6 changed files with 268 additions and 92 deletions

View File

@ -7,6 +7,8 @@ public enum LimboPersistenceType {
INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class),
SINGLE_FILE(SingleFilePersistenceHandler.class),
DISABLED(NoOpPersistenceHandler.class);
private final Class<? extends LimboPersistenceHandler> implementationClass;

View File

@ -0,0 +1,115 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.Location;
import org.bukkit.World;
import java.lang.reflect.Type;
import java.util.function.Function;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.CAN_FLY;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.FLY_SPEED;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GROUP;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.IS_OP;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOCATION;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_PITCH;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_WORLD;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_X;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Y;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_YAW;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Z;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.WALK_SPEED;
/**
* Converts a JsonElement to a LimboPlayer.
*/
class LimboPlayerDeserializer implements JsonDeserializer<LimboPlayer> {
private BukkitService bukkitService;
LimboPlayerDeserializer(BukkitService bukkitService) {
this.bukkitService = bukkitService;
}
@Override
public LimboPlayer deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject == null) {
return null;
}
Location loc = deserializeLocation(jsonObject);
boolean operator = getBoolean(jsonObject, IS_OP);
String group = getString(jsonObject, GROUP);
boolean canFly = getBoolean(jsonObject, CAN_FLY);
float walkSpeed = getFloat(jsonObject, WALK_SPEED, LimboPlayer.DEFAULT_WALK_SPEED);
float flySpeed = getFloat(jsonObject, FLY_SPEED, LimboPlayer.DEFAULT_FLY_SPEED);
return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed);
}
private Location deserializeLocation(JsonObject jsonObject) {
JsonElement e;
if ((e = jsonObject.getAsJsonObject(LOCATION)) != null) {
JsonObject locationObject = e.getAsJsonObject();
World world = bukkitService.getWorld(getString(locationObject, LOC_WORLD));
if (world != null) {
double x = getDouble(locationObject, LOC_X);
double y = getDouble(locationObject, LOC_Y);
double z = getDouble(locationObject, LOC_Z);
float yaw = getFloat(locationObject, LOC_YAW);
float pitch = getFloat(locationObject, LOC_PITCH);
return new Location(world, x, y, z, yaw, pitch);
}
}
return null;
}
private static String getString(JsonObject jsonObject, String memberName) {
JsonElement element = jsonObject.get(memberName);
return element != null ? element.getAsString() : "";
}
private static boolean getBoolean(JsonObject jsonObject, String memberName) {
JsonElement element = jsonObject.get(memberName);
return element != null && element.getAsBoolean();
}
private static float getFloat(JsonObject jsonObject, String memberName) {
return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, 0.0f);
}
private static float getFloat(JsonObject jsonObject, String memberName, float defaultValue) {
return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, defaultValue);
}
private static double getDouble(JsonObject jsonObject, String memberName) {
return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsDouble, 0.0);
}
/**
* Gets a number from the given JsonElement safely.
*
* @param jsonElement the element to retrieve the number from
* @param numberFunction the function to get the number from the element
* @param defaultValue the value to return if the element is null or the number cannot be retrieved
* @param <N> the number type
* @return the number from the given JSON element, or the default value
*/
private static <N extends Number> N getNumberFromElement(JsonElement jsonElement,
Function<JsonElement, N> numberFunction,
N defaultValue) {
if (jsonElement != null) {
try {
return numberFunction.apply(jsonElement);
} catch (NumberFormatException ignore) {
}
}
return defaultValue;
}
}

View File

@ -0,0 +1,52 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import fr.xephi.authme.data.limbo.LimboPlayer;
import org.bukkit.Location;
import java.lang.reflect.Type;
/**
* Converts a LimboPlayer to a JsonElement.
*/
class LimboPlayerSerializer implements JsonSerializer<LimboPlayer> {
static final String LOCATION = "location";
static final String LOC_WORLD = "world";
static final String LOC_X = "x";
static final String LOC_Y = "y";
static final String LOC_Z = "z";
static final String LOC_YAW = "yaw";
static final String LOC_PITCH = "pitch";
static final String GROUP = "group";
static final String IS_OP = "operator";
static final String CAN_FLY = "can-fly";
static final String WALK_SPEED = "walk-speed";
static final String FLY_SPEED = "fly-speed";
@Override
public JsonElement serialize(LimboPlayer limboPlayer, Type type, JsonSerializationContext context) {
Location loc = limboPlayer.getLocation();
JsonObject locationObject = new JsonObject();
locationObject.addProperty(LOC_WORLD, loc.getWorld().getName());
locationObject.addProperty(LOC_X, loc.getX());
locationObject.addProperty(LOC_Y, loc.getY());
locationObject.addProperty(LOC_Z, loc.getZ());
locationObject.addProperty(LOC_YAW, loc.getYaw());
locationObject.addProperty(LOC_PITCH, loc.getPitch());
JsonObject obj = new JsonObject();
obj.add(LOCATION, locationObject);
obj.addProperty(GROUP, limboPlayer.getGroup());
obj.addProperty(IS_OP, limboPlayer.isOperator());
obj.addProperty(CAN_FLY, limboPlayer.isCanFly());
obj.addProperty(WALK_SPEED, limboPlayer.getWalkSpeed());
obj.addProperty(FLY_SPEED, limboPlayer.getFlySpeed());
return obj;
}
}

View File

@ -3,26 +3,17 @@ 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;
/**
@ -32,19 +23,16 @@ 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.");
ConsoleLogger.warning("Failed to create playerdata directory '" + cacheDir + "'");
}
gson = new GsonBuilder()
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
.setPrettyPrinting()
.create();
}
@ -99,80 +87,4 @@ class SeparateFilePersistenceHandler implements LimboPersistenceHandler {
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;
}
}
}

View File

@ -0,0 +1,94 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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.entity.Player;
import javax.inject.Inject;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Saves all LimboPlayers in one JSON file and keeps the entries in memory.
*/
class SingleFilePersistenceHandler implements LimboPersistenceHandler {
private final File cacheFile;
private final Gson gson;
private Map<String, LimboPlayer> entries;
@Inject
SingleFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
cacheFile = new File(dataFolder, "limbo.json");
if (!cacheFile.exists()) {
FileUtils.create(cacheFile);
}
gson = new GsonBuilder()
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
.setPrettyPrinting()
.create();
Type type = new TypeToken<ConcurrentMap<String, LimboPlayer>>(){}.getType();
try (FileReader fr = new FileReader(cacheFile)) {
entries = gson.fromJson(fr, type);
} catch (IOException e) {
ConsoleLogger.logException("Failed to read from '" + cacheFile + "':", e);
}
if (entries == null) {
entries = new ConcurrentHashMap<>();
}
}
@Override
public LimboPlayer getLimboPlayer(Player player) {
return entries.get(PlayerUtils.getUUIDorName(player));
}
@Override
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
entries.put(PlayerUtils.getUUIDorName(player), limbo);
saveEntries("adding '" + player.getName() + "'");
}
@Override
public void removeLimboPlayer(Player player) {
LimboPlayer entry = entries.remove(PlayerUtils.getUUIDorName(player));
if (entry != null) {
saveEntries("removing '" + player.getName() + "'");
}
}
/**
* Saves the entries to the disk.
*
* @param action the reason for saving (for logging purposes)
*/
private void saveEntries(String action) {
try (FileWriter fw = new FileWriter(cacheFile)) {
gson.toJson(entries, fw);
} catch (IOException e) {
ConsoleLogger.logException("Failed saving JSON limbo cache after " + action, e);
}
}
@Override
public LimboPersistenceType getType() {
return LimboPersistenceType.SINGLE_FILE;
}
}

View File

@ -22,10 +22,11 @@ public final class LimboSettings implements SettingsHolder {
"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"
"DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file,",
"SINGLE_FILE: all data in one single file (only if you have a small server!)"
})
public static final Property<LimboPersistenceType> LIMBO_PERSISTENCE_TYPE =
newProperty(LimboPersistenceType.class, "limbo.persistence", LimboPersistenceType.INDIVIDUAL_FILES);
newProperty(LimboPersistenceType.class, "limbo.persistence.type", LimboPersistenceType.INDIVIDUAL_FILES);
@Comment({
"Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.",