ljacqu 8557621c02 #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
2017-03-12 18:43:37 +01:00

171 lines
6.3 KiB
Java

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;
import javax.inject.Inject;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_ALLOW_FLIGHT;
import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_FLY_SPEED;
import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_WALK_SPEED;
/**
* Service for managing players that are in "limbo," a temporary state players are
* put in which have joined but not yet logged in yet.
*/
public class LimboService {
private final Map<String, LimboPlayer> entries = new ConcurrentHashMap<>();
@Inject
private Settings settings;
@Inject
private LimboPlayerTaskManager taskManager;
@Inject
private LimboServiceHelper helper;
@Inject
private LimboPersistence persistence;
LimboService() {
}
/**
* Creates a LimboPlayer for the given player and revokes all "limbo data" from the player.
*
* @param player the player to process
* @param isRegistered whether or not the player is registered
*/
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}` already present in memory", name);
}
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer);
taskManager.registerMessageTask(player, limboPlayer, isRegistered);
taskManager.registerTimeoutTask(player, limboPlayer);
helper.revokeLimboStates(player);
entries.put(name, limboPlayer);
persistence.saveLimboPlayer(player, limboPlayer);
}
/**
* Returns the limbo player for the given name, or null otherwise.
*
* @param name the name to retrieve the data for
* @return the associated limbo player, or null if none available
*/
public LimboPlayer getLimboPlayer(String name) {
return entries.get(name.toLowerCase());
}
/**
* Returns whether there is a limbo player for the given name.
*
* @param name the name to check
* @return true if present, false otherwise
*/
public boolean hasLimboPlayer(String name) {
return entries.containsKey(name.toLowerCase());
}
/**
* Restores the limbo data and subsequently deletes the entry.
* <p>
* Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and
* changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}.
*
* @param player the player whose data should be restored
*/
public void restoreData(Player player) {
String lowerName = player.getName().toLowerCase();
LimboPlayer limbo = entries.remove(lowerName);
if (limbo == null) {
ConsoleLogger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName);
} else {
player.setOp(limbo.isOperator());
settings.getProperty(RESTORE_ALLOW_FLIGHT).restoreAllowFlight(player, limbo);
settings.getProperty(RESTORE_FLY_SPEED).restoreFlySpeed(player, limbo);
settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo);
limbo.clearTasks();
ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName);
persistence.removeLimboPlayer(player);
}
}
/**
* Creates new tasks for the given player and cancels the old ones for a newly registered player.
* This resets his time to log in (TimeoutTask) and updates the message he is shown (MessageTask).
*
* @param player the player to reset the tasks for
*/
public void replaceTasksAfterRegistration(Player player) {
getLimboOrLogError(player, "reset tasks")
.ifPresent(limbo -> {
taskManager.registerTimeoutTask(player, limbo);
taskManager.registerMessageTask(player, limbo, true);
});
}
/**
* Resets the message task associated with the player's LimboPlayer.
*
* @param player the player to set a new message task for
* @param isRegistered whether or not the player is registered
*/
public void resetMessageTask(Player player, boolean isRegistered) {
getLimboOrLogError(player, "reset message task")
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered));
}
/**
* @param player the player whose message task should be muted
*/
public void muteMessageTask(Player player) {
getLimboOrLogError(player, "mute message task")
.ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), true));
}
/**
* @param player the player whose message task should be unmuted
*/
public void unmuteMessageTask(Player player) {
getLimboOrLogError(player, "unmute message task")
.ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), false));
}
/**
* Returns the limbo player for the given player or logs an error.
*
* @param player the player to retrieve the limbo player for
* @param context the action for which the limbo player is being retrieved (for logging)
* @return Optional with the limbo player
*/
private Optional<LimboPlayer> getLimboOrLogError(Player player, String context) {
LimboPlayer limbo = entries.get(player.getName().toLowerCase());
if (limbo == null) {
ConsoleLogger.debug("No LimboPlayer found for `{0}`. Action: {1}", player.getName(), context);
}
return Optional.ofNullable(limbo);
}
}